Servlet文件上传与下载

一、知识点:

1、jsp+servlet实现文件的上传表单注意事项:

    1)表单 method 属性应该设置为 POST 方法,不能使用 GET 方法。

    2)表单 enctype 属性应该设置为 multipart/form-data.

    3)表单 action 属性应该设置为在后端服务器上处理文件上传的 Servlet 文件。

    4)上传单个文件,您应该使用单个带有属性 type="file" 的 <input .../> 标签。为了允许多个文件上传,请包含多个 name 属性值不同的 input 标签。输入标签具有不同的名称属性的值。浏览器会为每个 input 标签关联一个浏览按钮。 

2、什么是multipart/form-data?

enctype属性:规定了form表单在发送到服务器时候编码方式。有如下三个值。

    1)application/x-www-form-urlencoded。默认的编码方式。但是在用文本的传输,大型文件的时候,使用这种编码就显得 效率低下。

    2)multipart/form-data 。 指定传输数据为二进制类型,比如图片、mp3、文件。

    3)text/plain。纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。

3、上传文件大小超过设置时,浏览器连接被重置

    这个和Tomcat默认设置有关,server.xml中有个重要参数,

    maxSwallowSize 默认为2M,改为-1表示无限制,修改后:

<Connector connectionTimeout="20000" 
		port="80" 
		protocol="HTTP/1.1" 
		redirectPort="8443" 
		URIEncoding="UTF-8"
		connectionUploadTimeout="36000000"
		disableUploadTimeout="false" maxSwallowSize="-1"/>  

二、使用Apache文件上传 fileupload 组件

1、DiskFileItemFactory 类

       将请求消息实体中的每一个项目封装成单独的DiskFileItem (FileItem接口的实现) 对象的任务。由 org.apache.commons.fileupload.FileItemFactory 接口的默认实现 ,org.apache.commons.fileupload.disk.DiskFileItemFactory 来完成。

       当上传的文件项目比较小时,直接保存在内存中(速度比较快),比较大时,以临时文件的形式,保存在磁盘临时文件夹(虽然速度慢些,但是内存资源是有限的)。

属性:

1) public static final int DEFAULT_SIZE_THRESHOLD :将文件保存在内存还是磁盘临时文件夹的默认临界值,值为10240,即10kb。

2) private File repository:用于配置在创建文件项目时,当文件项目大于临界值时使用的临时文件夹,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir获取。如下代码:System.getProperty("java.io.tmpdir");

3) private int sizeThreshold:用于保存将文件保存在内存还是磁盘临时文件夹的临界值

构造方法:

1) public DiskFileItemFactory()

      采用默认临界值和系统临时文件夹构造文件项工厂对象。

2) public DiskFileItemFactory(int sizeThreshold,File repository)

      采用参数指定临界值和系统临时文件夹构造文件项工厂对象。

3) FileItem createItem()

       根据DiskFileItemFactory相关配置将每一个请求消息实体项目创建成DiskFileItem 实例,并返回。该方法从来不需要我们亲自调用,FileUpload组件在解析请求时内部使用。

4) void setSizeThreshold(int sizeThreshold)

        Apache文件上传组件在解析上传数据中的每个字段内容时,需要临时保存解析出的数据,以便在后面进行数据的进一步处理(保存在磁盘特定位置或插入数据库)。因为Java虚拟机默认可以使用的内存空间是有限的,超出限制时将会抛出“java.lang.OutOfMemoryError”错误。如果上传的文件很大,例如800M的文件,在内存中将无法临时保存该文件内容,Apache文件上传组件转而采用临时文件来保存这些数据;但如果上传的文件很小,例如600个字节的文件,显然将其直接保存在内存中性能会更加好些。

        setSizeThreshold方法用于设置是否将上传文件已临时文件的形式保存在磁盘的临界值(以字节为单位的int值),如果从没有调用该方法设置此临界值,将会采用系统默认值10KB。对应的 getSizeThreshold() 方法用来获取此临界值。

5) void setRepository(File repository)

        setRepositoryPath方法用于设置当上传文件尺寸大于setSizeThreshold方法设置的临界值时,将文件以临时文件形式保存在磁盘上的存放目录。有一个对应的获得临时文件夹的 File getRespository() 方法。

         注意:当从没有调用此方法设置临时文件存储目录时,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir 获取。如下代码:System.getProperty("java.io.tmpdir");

        Tomcat系统默认临时目录为“<tomcat安装目录>/temp/”。​ 

       另外,因为Struts2的上传功能也引入了该包,但是却做了拦截器限制文件的最大上传大小为2M,可以通过修改它的配置文件动态更改上传文件的大小;而且,Struts会在文件上传成功后,帮你删除掉临时文件。

       使用的是Spring MVC,需要在上传类中指定最大上传文件大小,(这玩意一般不动态设置,最大上传文件大小一般是系统的标准,是让使用者遵循滴),而且必须在上传成功后删除临时文件。

2、ServletFileUpload类:

 1)public void setSizeMax(long sizeMax)

        setSizeMax方法继承自FileUploadBase类,用于设置请求消息实体内容(即所有上传数据)的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。在请求解析的过程中,如果请求消息体内容的大小超过了setSizeMax方法的设置值,将会抛出FileUploadBase内部定义的SizeLimitExceededException异常(FileUploadException的子类)。该方法有一个对应的读方法:public long getSizeMax()方法。

  2) public void setFileSizeMax(long fileSizeMax)

       setFileSizeMax方法继承自FileUploadBase类,用于设置单个上传文件的最大尺寸限制,以防止客户端恶意上传超大文件来浪费服务器端的存储空间。其参数是以字节为单位的long型数字。该方法有一个对应的读方法:public long geFileSizeMax()方法。在请求解析的过程中,如果单个上传文件的大小超过了setFileSizeMax方法的设置值,将会抛出FileUploadBase内部定义的FileSizeLimitExceededException异常(FileUploadException的子类)。

 3) public List parseRequest(javax.servlet.http.HttpServletRequest req)

       parseRequest 方法是ServletFileUpload类的重要方法,它是对HTTP请求消息体内容进行解析的入口方法。它解析出FORM表单中的每个字段的数据,并将它们分别包装成独立的FileItem对象,然后将这些FileItem对象加入进一个List类型的集合对象中返回。

       该方法抛出FileUploadException异常来处理诸如文件尺寸过大、请求消息中的实体内容的类型不是“multipart/form-data”、IO异常、请求消息体长度信息丢失等各种异常。每一种异常都是FileUploadException的一个子类型。

 4)public FileItemIterator getItemIterator(HttpServletRequest request)

       getItemIterator方法和parseRequest 方法基本相同。但是getItemIterator方法返回的是一个迭代器,该迭代器中保存的不是FileItem对象,而是FileItemStream 对象,如果你希望进一步提高新能,你可以采用getItemIterator方法,直接获得每一个文件项的数据输入流,做底层处理;如果性能不是问题,你希望代码简单,则采用parseRequest方法即可。

 5) public stiatc boolean isMultipartContent(HttpServletRequest req)

       用于判断请求消息中的内容是否是“multipart/form-data”类型,是则返回true,否则返回false。isMultipartContent方法是一个静态方法,不用创建ServletFileUpload类的实例对象即可被调用。

 6) getFileItemFactory()和setFileItemFactory(FileItemFactory)

       方法继承自FileUpload类,用于设置和读取fileItemFactory属性。

 7) public void setProgressListener(ProgressListener pListener)

       设置文件上传进度监听器。该方法有一个对应的读取方法:ProgressListener getProgressListener()。

 8) public void setHeaderEncoding(String encode)

        在文件上传请求的消息体中,除了普通表单域的值是文本内容以外,上传文件中的文件路径名也是文本(包括文件名),在内存中保存的是它们的某种字符集编码的字节数组,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。

        setHeaderEncoding方法继承自FileUploadBase类,用于设置上面提到的字符编码。如果没有设置,则对应的读方法getHeaderEncoding()方法返回null,将采用HttpServletRequest设置的字符编码,如果HttpServletRequest的字符编码也为null,则采用系统默认字符编码。可以通过一下语句获得系统默认字符编码:System.getProperty("file.encoding"));

3、FileItem类

在HTML页面input  必须有 name  <input type="file" name="filename">

表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-data,

<form action =" <%=request.getContextPath()%>/upload" enctype="multipart/form-data" method="post">

浏览器表单的类型如果为multipart/form-data,那么浏览器在提交表单数据时,它将采用MIME协议对数据进行封装后提交,在服务器这端也将不能采用原来传统的方工获取数据了。在服务器端想获取数据就要通过流。request提供了一个getInputStream读取流。

常用方法:

1)boolean isFormField()

       用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。因此,可以使用该方法判断是否为普通表单域,还是文件上传表单域

2)String getName()

       用于获得文件上传字段中的文件名。

注意:IE或非IE中获取的文件名是不一样的,IE中是绝对路径,非IE中只是文件名。

3)String getFieldName()

       用于返回表单标签name属性的值。如<input type="text" name="column" />的value。

4)void write(File file)

       用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。

5)String getString()

      用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:

      public Java.lang.String getString()

      public java.lang.String getString(java.lang.String encoding)             throws java.io.UnsupportedEncodingException

      前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。

6)String getContentType()

       用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。

7)boolean isInMemory()

        用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。

8)void delete()

       delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。

       尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。另外,当系统出现异常时,仍有可能造成有的临时文件被永久保存在了硬盘中。

9)InputStream getInputStream()

       以流的形式返回上传文件的数据内容。

10)long getSize()

       返回该上传文件的大小(以字节为单位)。

三、文件上传

web.xml 中注册servlet

	<h4>文件上传 </h4>
	<form action="<%=request.getContextPath() %>/upload" method="post" enctype="multipart/form-data">
    	文件描述:  <input type="text" name="fileDescription" id="fileDescription"><br/>
		上传文件1:<input type="file" name="file1"><br/>
		上传文件2:<input type="file" name="file2"><br/>
		<input type="submit" value="提交" class="sbttn">
    </form>
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String message = "";
		//1. 获取和创建保存文件的最终目录和临时目录
		String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
		String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
		File tempFile = new File(tempPath);
        if (!tempFile.exists() && !tempFile.isDirectory()) { //判断上传文件的保存目录是否存在
            System.out.println(savePath+"目录不存在,需要创建");
            tempFile.mkdirs(); //创建目录
        }
        
        //使用Apache文件上传组件处理文件上传步骤:
		//2. 创建一个DiskFileItemFactory工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(100*1024); //放到内存中的大小:100KB
        factory.setRepository(tempFile);
		//3. 创建一个文件上传解析器
        ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
        servletFileUpload.setHeaderEncoding("UTF-8");	//防止上传文件名的中文乱码
        servletFileUpload.setFileSizeMax(20*1024*1024); //限制单个文件的大小:20M
        servletFileUpload.setSizeMax(40*1024*1024);	//限制多个文件同时上传总的大小:40M
        servletFileUpload.setProgressListener(new ProgressListener() {
			@Override
			public void update(long arg0, long arg1, int arg2) {
				System.out.println("已上传大小值:" + arg0 +",文件总大小值:" +arg1);
			}
		});
        
		//4. 判断提交上来的数据是否是上传表单的数据,是不是Multipart编码方式
        if(!ServletFileUpload.isMultipartContent(request)){
            return; //按照传统方式获取数据 key-value
        }
        
        //5. 使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合
        try {
			List<FileItem> fileList = servletFileUpload.parseRequest(request);
			for (FileItem fileItem : fileList) {
				//6. 判断fileitem中封装普通域还是文件域
				if(fileItem.isFormField()) {
					String name = fileItem.getFieldName();
                    String value = fileItem.getString("UTF-8"); //解决普通域数据的中文乱码
                    //value = new String(value.getBytes("iso8859-1"),"UTF-8");
                    System.out.println("普通域表单数据:" + name + "=" + value);
				}else {
					String fileName = fileItem.getName(); //获取上传的文件名称
					if(fileName==null || fileName.trim().equals("")){
						continue;
					}
					System.out.println(fileName);
					//注意事项:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:  c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
					fileName = fileName.substring(fileName.lastIndexOf("\\")+1); //只保留文件名部分 
					String fileType = fileItem.getContentType();//获取文件类型:image/jpg等
					long fileSize = fileItem.getSize();	  //获取文件总大小
					String fileEx = fileName.substring(fileName.lastIndexOf(".")+1); //文件后缀名
					//7. 验证后缀名的合法性
					if(fileEx.equals("rar ") || fileEx.equals("zip")) {
						throw new RuntimeException("禁止上传压缩文件");
					}
					//8. 将文件流写入保存的目录中(生成新的文件名,避免一个目录中文件太多而生成新的存储目录)
					String saveFileName = makeFileName(fileName);
                    String realSavePath = makePath(saveFileName, savePath);
                    InputStream in = fileItem.getInputStream(); //获取fileItem中上传文件的输入流
                    FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFileName);
					byte[] buffer = new byte[1024];
					int len = -1;
					while ((len = in.read(buffer)) != -1) {
						//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
						out.write(buffer, 0, len);
					}
					in.close();
					out.close();
					fileItem.delete(); // 请务必调用,在文件上传结束后,删除临时目录的文件
					message = "文件上传成功!";
				}
			}
			//9. 删除临时目录下的文件
			File  temp = new File(tempPath);
			for(File file : temp.listFiles()) {
				file.delete();
			}
		} catch (FileUploadBase.FileSizeLimitExceededException e) {
			e.printStackTrace();
			request.setAttribute("message", "单个文件超出最大值!!!");
			request.getRequestDispatcher("/message.jsp").forward(request, response);
			return;
		}catch (FileUploadBase.SizeLimitExceededException e) {
			e.printStackTrace();
			request.setAttribute("message", "上传文件的总的大小超出限制的最大值!!!");
			request.getRequestDispatcher("/message.jsp").forward(request, response);
			return;
		}catch (Exception e) {
			 message= "文件上传失败!";
			 e.printStackTrace();
		}
		
        request.setAttribute("message",message);
        request.getRequestDispatcher("/message.jsp").forward(request, response);
	}

	private String makePath(String saveFileName, String savePath) {
		// 获取文件名字,在内存中的地址,hashCode值
		int hashCode = saveFileName.hashCode();
		int dir1 = hashCode & 0xf; // 与运算结果:[0,15]
		int dir2 = (hashCode & 0xf0) >> 4 ; // 运算结果:[0,15]
		String dir = savePath + File.separator + dir1 + File.separator + dir2;
		File file = new File(dir);
		if(!file.exists() && !file.isDirectory()) {
			file.mkdirs();
		}
		return dir;
	}

	private String makeFileName(String fileName) {
		return UUID.randomUUID().toString() + fileName;
	}

四、文件下载

web.xml 中注册servlet

文件名乱码参考文章:解决各大浏览器下载文件,文件名乱码的问题

    用户Table基本信息.xlsx<a href="<%=request.getContextPath() %>/download">下载</a>
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1. 获取下载文件的绝对路径:
		String filePath = "E:\\java\\file01\\用户Table基本信息.xlsx";
		String fileName = "用户Table基本信息.xlsx";
		
		//从request中获取浏览器的信息
		String userAgent = request.getHeader("User-Agent");
		//IE:   Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134
		//谷歌: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36
		//360:  Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
		//火狐:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
		//2. 防止文件名乱码:针对IE或者以IE为内核的浏览器:
		if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {
			fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
			System.out.println("IE");
		} else {//非IE浏览器的处理:
			fileName = new String(fileName.getBytes("UTF-8"),"ISO-8859-1");
			System.out.println("非IE");
		}
		//3. 设置content-disposition响应头控制浏览器以下载的方式打开文件
		response.setHeader("content-disposition","attachment;filename="+fileName);
		
		//4. 获取要下载的文件输入流,通过response对象获取OutputStream输出流对象
		InputStream in = new FileInputStream(filePath);
		OutputStream out = response.getOutputStream();
		int len = 0;
		byte[] buffer = new byte[1024];
		while((len=in.read(buffer))>0){
			out.write(buffer,0,len);
		}
		in.close();
		out.close();
	}

 

  

参考文章:https://www.cnblogs.com/xdp-gacl/p/4200090.html

猜你喜欢

转载自blog.csdn.net/qq_42402854/article/details/86219056