文件上传
2026/9/12大约 4 分钟
文件上传
本篇整理 JavaWeb 中的文件上传功能:上传对表单和 Servlet 的要求、使用 commons-fileupload 组件实现上传的步骤,以及乱码和同名文件两个常见细节问题的处理。
1. 简介
在项目中,文件上传是常见的功能,很多程序或软件中都会使用到,例如:
- 邮箱中的附件上传;
- 图片上传。
2. 什么是文件上传
当用户在前端页面点击文件上传后,用户上传的文件数据提交给服务器端,由服务器实现保存。
3. 上传的相关限制
3.1 上传对表单的限制
上传表单必须满足以下三个条件:
- 请求方式为
method="post"; - 编码类型为
enctype="multipart/form-data"; - 表单中需要添加文件表单项:
<input type="file" name="xxx" />。
<form action="xxx" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
照片:<input type="file" name="photo"/><br/>
<input type="submit" value="上传"/>
</form>3.2 上传对 Servlet 的限制
当表单的编码类型为 enctype="multipart/form-data" 时,request.getParameter("xxx") 这个方法就作废了:它永远都返回 null,因为请求数据不再是普通的键值对格式,而是 multipart 格式。
3.3 如何实现文件上传
3.3.1 相关 jar 包
实现上传需要两个 jar 包:
- commons-fileupload-1.4.jar
- commons-io-2.2.jar
commons-fileupload 会帮我们解析 request 中的上传数据,解析结果是把每个表单项的数据封装到一个 FileItem 对象中,我们只需要调用 FileItem 的方法即可。
3.3.2 具体步骤
- 创建工厂:
DiskFileItemFactory factory = new DiskFileItemFactory(); - 创建解析器:
ServletFileUpload sfu = new ServletFileUpload(factory); - 使用解析器解析
request,得到FileItem集合:List<FileItem> fileItemList = sfu.parseRequest(request)。
FileItem 的常用方法:
| 方法 | 说明 |
|---|---|
boolean isFormField() | 是否为普通表单项:返回 true 为普通表单项,返回 false 为文件表单项 |
String getFieldName() | 返回当前表单项的名称 |
String getString(String charset) | 返回表单项的值,可指定编码解决乱码 |
String getName() | 返回上传的文件名称 |
long getSize() | 返回上传文件的字节数 |
InputStream getInputStream() | 返回上传文件对应的输入流 |
void write(File destFile) | 把上传的文件内容保存到指定文件中 |
完整的上传 Servlet 如下:
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
@WebServlet(name = "UploadServlet", value = "/UploadServlet")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
try {
// 使用解析器解析 request,得到 FileItem 集合
List<FileItem> list = sfu.parseRequest(request);
for (FileItem item : list) {
if (item.isFormField()) {
// 普通表单项
String fieldName = item.getFieldName();
String value = item.getString("utf-8");
System.out.println(fieldName + ":" + value);
} else {
// 文件表单项
String fileName = item.getName();
long size = item.getSize();
System.out.println("文件名:" + fileName);
System.out.println("文件大小:" + size);
// 获取 /img 目录在服务器上的真实路径
ServletContext servletContext = this.getServletConfig().getServletContext();
String realPath = servletContext.getRealPath("/img");
// 把上传的文件保存到服务器
item.write(new File(realPath + "/" + fileName));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}3.4 需要注意的细节
3.4.1 文件名或普通表单项乱码
解决乱码有两种方案,二选一即可:
request.setCharacterEncoding("utf-8");servletFileUpload.setHeaderEncoding("utf-8");
以上面的 UploadServlet 为例,只需在解析之前加上一行设置:
// 创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
// 方案一:设置请求编码
request.setCharacterEncoding("utf-8");
// 方案二(与方案一二选一):
// sfu.setHeaderEncoding("utf-8");
// 后续解析与保存代码与上面相同3.4.2 同名文件上传问题
如果多个用户上传了同名文件,后上传的文件会覆盖先上传的文件。常见的解决方案是使用 UUID 给文件名加上唯一前缀:
fileName = UUID.randomUUID().toString().replace("-", "") + "_" + fileName;在保存文件前先重命名即可:
String fileName = item.getName();
// 用 UUID 重命名,避免同名文件相互覆盖
fileName = UUID.randomUUID().toString().replace("-", "") + "_" + fileName;
ServletContext servletContext = this.getServletConfig().getServletContext();
String realPath = servletContext.getRealPath("/img");
item.write(new File(realPath + "/" + fileName));注
使用 UUID 重命名后,文件名仍然保留了原始名称作为后缀(uuid_原文件名),这样既保证唯一性,又方便管理员辨认原始文件名。
