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 包文件。

搭建好的项目工程整体目录比较简单,具体如下图所示:

image

项目工程结构简单介绍:

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;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public 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();
}
}
@Override
public void destroy() {
}
}

upLoadServlet 是专门用来处理上传文件的请求,同时也接收 form 表单提交过来的字段,具体内容如下:

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 {
@Override
protected 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 {
@Override
protected 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>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件测试</title>
</head>
<body>
<form id="Form1" action="/api/upload" method="post" enctype="multipart/form-data">
<table>
<caption>表单提交,文件上传,支持中文文件名称</caption>
<tr>
<td>请输入内容:</td>
<td><input type="text" name="submitName"></td>
</tr>
<tr>
<td>请选择文件01:</td>
<td><input type="file" name="upFile1"></td>
</tr>
<tr>
<td>请选择文件02:</td>
<td><input type="file" name="upFile2"></td>
</tr>
<tr>
<td></td>
<td>
<button type="submit">提交</button>
<button type="reset">重置</button>
</td>
</tr>
</table>
</form>
<br/>
<form id="Form2" action="/api/download" method="post">
<table>
<caption>文件下载,支持中文文件名称</caption>
<tr>
<td>请输入下载后的文件命名(不需要输入后缀名):</td>
<td><input type="text" name="downLoadName"></td>
</tr>
<tr>
<td>点击按钮下载中文名称的文件:</td>
<td>
<button type="submit">下载测试文件</button>
</td>
</tr>
</table>
</form>
</body>
</html>

该页面的界面如下所示:

image

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

本 demo 示例的代码下载地址为:https://files.cnblogs.com/files/blogs/699532/fileUpDownTest.zip



posted @   乔京飞  阅读(10792)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示