ARTICLE · 1207315
Java 使用 Servlet 上传下载文件
在实际开发中,上传文件和下载文件是很常见的功能,如果文件名是中文的话,还容易会出现乱码问题。
本篇博客采用 Servlet 作为接口演示 Java 上传文件和下载文件的实现方案,同时解决获取上传和下载过程中所遇到的的中文文件名乱码问题,并在本篇博客的最下面提供 demo 源代码下载。
一、搭建工程
新建一个 maven 工程,并导入相关的 jar 包和 tomcat 插件,具体内容如下:
有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。
有关 tomcat 插件,可以到官网查询,地址为:https://tomcat.apache.org/maven-plugin.html
目前 tomcat 官网的插件,最新版本也就是 tomcat7 的 2.2 版本。如果你想使用更高版本的 tomcat 的话,就下载独立的 tomcat 即可。这里只是进行 demo 演示,就直接使用 tomcat 的插件了,能够满足 demo 运行的环境,使用起来也很方便。
<dependencies><!--servlet支持--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!--文件上传组件支持--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency><!--Apache提供的一些实用的工具类支持--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency></dependencies><build><finalName>fileUpDownTest</finalName><plugins><!--Tomcat官网最新版本的tomcat插件--><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><!--tomcat启动的端口号--><port>80</port><!--网站启动后访问的虚拟地址--><path>/</path></configuration></plugin></plugins></build>
由于要使用 servlet web 开发,需要导入 javax.servlet-api 的 jar 包。另外导入了 Apache 的 commons-fileupload 的 jar 包组件,使文件上传变的很容易,同时导入了 commons-lang3 的 jar 包组件,主要是使用里面的一些字符串处理的实用方法,简化代码开发。最后打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。
搭建好的项目工程整体目录比较简单,具体如下图所示:

项目工程结构简单介绍:
com.jobs.controller 包下面放了 2 个 Servlet 处理上传和下载的请求
com.jobs.filter 包下面放了 1 个过滤器,主要设置请求和响应的字符编码,统一解决乱码问题
webapp 下的 upload 用来存放上传的文件,里面提前放了一个中文名称的 txt 文件,用于下载
webapp 下面放了一个 index.html 页面文件,网站启动时进行展示,提供文件上传和下载操作
二、细节展示
这里先说 CharacterEncodingFilter 过滤器,统一设置请求和响应的字符编码为 UTF-8 ,这样可以方式乱码。另外该过滤器配置的作用地址,本 demo 示例只处理 Servlet 请求,不处理其它请求,例如 css、js 等文件请求。具体内容如下:
package com.jobs.filter;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.annotation.WebInitParam;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;//这里只监听 /api/* 的请求,如果你想监听所有请求,可以使用 /* 配置//另外配置了过滤器的请求和响应的初始化参数,在下面的处理方法中可以获取@WebFilter(value = "/api/*",initParams = {@WebInitParam(name = "requestEncoding", value = "UTF-8"),@WebInitParam(name = "responseContentType", value = "text/html;charset=UTF-8")})public class CharacterEncodingFilter implements Filter {private FilterConfig filterConfig;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {this.filterConfig = filterConfig;}@Overridepublic void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest) req;response = (HttpServletResponse) resp;//获取初始化参数String requestEncoding = filterConfig.getInitParameter("requestEncoding");String responseContentType = filterConfig.getInitParameter("responseContentType");//使用初始化参数,设置请求和响应的编码request.setCharacterEncoding(requestEncoding);response.setContentType(responseContentType);chain.doFilter(request, response);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void destroy() {}}
package com.jobs.controller;import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;import org.apache.commons.lang3.StringUtils;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.*;import java.util.List;//配置访问地址@WebServlet("/api/upload")public class upLoadServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest request,HttpServletResponse resp) throws IOException {//判断该操作是否支持文件上传操作//要提交的form表单上必须要有属性 enctype="multipart/form-data"if (ServletFileUpload.isMultipartContent(request)) {try {//将文件保存到 webapp 下的 upload 文件夹中String dir = this.getServletContext().getRealPath("upload");File f = new File(dir);if (!f.exists()) {f.mkdir();}//采用 apache 的上传组件,只需要编写很少的代码,即可完成文件上传功能DiskFileItemFactory factory = new DiskFileItemFactory();ServletFileUpload fileUpload = new ServletFileUpload(factory);List<FileItem> fileItems = fileUpload.parseRequest(request);for (FileItem item : fileItems) {//当前表单是否是文件表单if (item.isFormField()) {//获取表单字段String fieldName = item.getFieldName();//必须使用 utf-8 编码获取表单字段的值,否则就是乱码String fieldValue = item.getString("utf-8");System.out.println("表单字段:" + fieldName + "," + fieldValue);} else {//此 item 表示文件//获取文件的表单字段名称String fieldName = item.getFieldName();//获取文件的原始文件名,不包含路径String fileName = item.getName();System.out.println("上传文件:" + fieldName + "," + fileName);//这里的 StringUtils 是 apache 的 commons-lang3 组件提供的实用工具类if(StringUtils.isNotEmpty(fileName)) {//将上传的文件,以原始名称保存到具体的目录下,//当然你可以生成随机 uuid 给文件命名//这里使用原始文件名,主要演示由于设置了过滤器统一处理字符编码,//所以保存中文文件名,不出现乱码的问题item.write(new File(dir, fileName));}}}} catch (Exception ex) {ex.printStackTrace();}}//由于过滤器统一设置了响应编码,//所以响应页面中展示的中文,不会出现乱码resp.getWriter().write("恭喜你,成功了");}}
需要注意:由于本 demo 示例采用了 tomcat 插件,因此运行的是源码本身的网站上传文件,文件会保存到 webapp 下的 upload 文件夹下,在 idea 工具中可以立刻看到。如果你使用的是独立的 tomcat 进行部署访问时,上传文件后需要到独立部署的目标 webapp 下的 upload 文件夹下去查看上传的文件。
下面列出 downLoadServlet 的内容,这个是专门用来处理下载文件请求的,具体细节如下:
package com.jobs.controller;import org.apache.commons.lang3.StringUtils;import javax.servlet.ServletException;import javax.servlet.ServletOutputStream;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.*;//配置访问地址@WebServlet("/api/download")public class downLoadServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req,HttpServletResponse response) throws ServletException, IOException {String parm = req.getParameter("downLoadName");String downLoadName;if (StringUtils.isNotBlank(parm)) {downLoadName = parm + ".txt";} else {downLoadName = "测试文件.txt";}//获取所要下载的文件,这里只是 demo 演示,因此对下载的目标文件进行了硬编码String dir = this.getServletContext().getRealPath("upload");File f = new File(dir, "测试文件.txt");//下载文件的响应类型,这里统一设置成了文件流//你可以根据自己所提供下载的文件类型,使用不同的响应 mime 类型response.setContentType("application/octet-stream;charset=utf-8");//设置下载弹出框中默认显示的文件名称,如果指定中文名称的话,需要转成 iso8859-1 编码,解决乱码问题String fileName = new String(downLoadName.getBytes(), "iso8859-1");response.addHeader("Content-Disposition", "attachment;filename=" + fileName);//第一种下载方式,适合文件已经在硬盘上,此时读取文件字节流,直接下载try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f))) {ServletOutputStream outputStream = response.getOutputStream();byte[] bArr = new byte[1024];int len;while ((len = bis.read(bArr)) != -1) {outputStream.write(bArr, 0, len);}} catch (IOException e) {e.printStackTrace();}/*//第二种下载方式,适合不在硬盘上生成文件,而是在内存中生成文件字节流,然后下载//当然这里的例子,还是从硬盘上读取了文件,但是将字节流存入 ByteArrayOutputStream 内存字节流//模拟使用 ByteArrayOutputStream 生成文件流,然后进行下载ServletOutputStream outputStream = response.getOutputStream();try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));ByteArrayOutputStream baos=new ByteArrayOutputStream()){byte[] bArr = new byte[1024];int len;while ((len = bis.read(bArr)) != -1) {baos.write(bArr, 0, len);}baos.writeTo(outputStream);} catch(IOException e){e.printStackTrace();}outputStream.flush();*/}}
本 demo 下载的是 webapp 下的 upload 文件夹下的固定文件:测试文件.txt
本 demo 提供了两种下载的方式,第一种是读取硬盘中的文件,采用响应流进行下载。
第二种方式是模拟不生成硬盘文件的前提下,采用内存流的方式生成文件,并最终写入响应流,还是依靠响应流进行下载。
当然本 demo 还是从硬盘中读取了文件,生成该文件的内存流。在实际开发中,我们可能会从数据库中获取记录,然后在内存中生成 Excel 并写入内存流,然后通过响应流进行下载 Excel 文件。
最后展示 index.html 页面的具体内容:
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>上传文件测试</title></head><body><formid="Form1"action="/api/upload"method="post"enctype="multipart/form-data"><table><caption>表单提交,文件上传,支持中文文件名称</caption><tr><td>请输入内容:</td><td><inputtype="text"name="submitName"></td></tr><tr><td>请选择文件01:</td><td><inputtype="file"name="upFile1"></td></tr><tr><td>请选择文件02:</td><td><inputtype="file"name="upFile2"></td></tr><tr><td></td><td><buttontype="submit">提交</button><buttontype="reset">重置</button></td></tr></table></form><br/><formid="Form2"action="/api/download"method="post"><table><caption>文件下载,支持中文文件名称</caption><tr><td>请输入下载后的文件命名(不需要输入后缀名):</td><td><inputtype="text"name="downLoadName"></td></tr><tr><td>点击按钮下载中文名称的文件:</td><td><buttontype="submit">下载测试文件</button></td></tr></table></form></body></html>
该页面的界面如下所示:

对于上传文件的表单,文本框中可以录入中文,可以同时上传 2 个文件,上传所选择的文件也可以是中文名称的文件。服务器接收到的表单字段内容,以及文件名称,可以正常接收到中文,不会出现乱码。对于下载文件的表单,可以在文本框中录入你想看到的下载文件名称,可以是中文,不会出现乱码问题。















































































