1. 概述

本文将介绍如何在 Servlet 中实现文件上传功能。我们主要介绍两种主流方式:

✅ 原生 Jakarta EE 提供的 @MultipartConfig 注解(推荐用于 Servlet 3.0+)
✅ Apache Commons FileUpload 库(适用于老版本 Servlet API)

如果你的项目基于较新的 Jakarta EE(或旧称 Java EE),优先使用第一种方式,简单、干净、无需额外依赖。只有在维护老系统时才考虑使用 Commons FileUpload。


2. 使用 Jakarta EE 的 @MultipartConfig

从 Servlet 3.0 开始,原生支持多部分请求(multipart request),无需引入第三方库。这是目前最简单粗暴的实现方式。

2.1 HTML 表单

首先,前端表单必须设置 enctype="multipart/form-data",否则文件无法正确传输:

<form method="post" action="multiPartServlet" enctype="multipart/form-data">
    选择文件: <input type="file" name="file" />
    <input type="submit" value="上传" />
</form>

⚠️ 注意:name="file" 是后端获取 Part 时的关键标识。

2.2 配置 MultipartConfig

在你的 HttpServlet 上添加 @MultipartConfig 注解,用于设置上传限制和临时存储策略:

@MultipartConfig(
    fileSizeThreshold = 1024 * 1024,      // 1MB,超过则写入磁盘
    maxFileSize = 1024 * 1024 * 5,        // 单文件最大 5MB
    maxRequestSize = 1024 * 1024 * 5 * 5  // 整个请求最大 25MB(支持多文件)
)
public class MultipartServlet extends HttpServlet {
    private static final String UPLOAD_DIRECTORY = "uploads";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        
        String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) uploadDir.mkdir();

        for (Part part : request.getParts()) {
            String fileName = getFileName(part);
            if (fileName != null && !fileName.isEmpty()) {
                part.write(uploadPath + File.separator + fileName);
            }
        }

        response.getWriter().println("上传成功!");
    }

    private String getFileName(Part part) {
        for (String content : part.getHeader("content-disposition").split(";")) {
            if (content.trim().startsWith("filename")) {
                return content.substring(content.indexOf("=") + 2, content.length() - 1);
            }
        }
        return null;
    }
}

2.3 更简洁的获取文件名方式(Servlet 3.1+)

如果你使用的是 Servlet 3.1 或更高版本,可以直接调用 getSubmittedFileName(),无需手动解析 header:

String fileName = part.getSubmittedFileName();

✅ 推荐:这种方式更安全,避免了手动字符串处理可能带来的 bug。


3. 使用 Apache Commons FileUpload

如果你还在维护一个古老的 Servlet 2.5 项目,无法使用 @MultipartConfig,那么 Apache Commons FileUpload 是经典选择。

3.1 依赖配置

pom.xml 中添加以下依赖:

<dependency> 
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.13.0</version>
</dependency>

⚠️ 注意:commons-ioFileUpload 的运行时依赖,漏掉会抛 NoClassDefFoundError

3.2 实现上传 Servlet

Commons FileUpload 的核心流程如下:

  1. 判断请求是否为 multipart
  2. 配置 DiskFileItemFactory(内存/磁盘缓存策略)
  3. 使用 ServletFileUpload 解析请求
  4. 遍历 FileItem 并保存文件
public class FileUploadServlet extends HttpServlet {
    private static final int MEMORY_THRESHOLD = 1024 * 1024;     // 1MB
    private static final long MAX_FILE_SIZE = 1024 * 1024 * 5;   // 5MB
    private static final long MAX_REQUEST_SIZE = 1024 * 1024 * 25; // 25MB
    private static final String UPLOAD_DIRECTORY = "uploads";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        if (!ServletFileUpload.isMultipartContent(request)) {
            response.getWriter().println("请求不是 multipart 类型");
            return;
        }

        // 配置工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

        // 创建上传处理器
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(MAX_FILE_SIZE);
        upload.setSizeMax(MAX_REQUEST_SIZE);

        // 创建上传目录
        String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) uploadDir.mkdir();

        try {
            List<FileItem> formItems = upload.parseRequest(request);
            if (formItems != null && !formItems.isEmpty()) {
                for (FileItem item : formItems) {
                    if (!item.isFormField()) { // 只处理文件字段
                        String fileName = new File(item.getName()).getName();
                        String filePath = uploadPath + File.separator + fileName;
                        File storeFile = new File(filePath);
                        item.write(storeFile);
                        request.setAttribute("message", "文件 " + fileName + " 上传成功!");
                    }
                }
            }
        } catch (Exception ex) {
            request.setAttribute("message", "上传失败: " + ex.getMessage());
        }

        response.getWriter().println(request.getAttribute("message"));
    }
}

✅ 踩坑提示:

  • item.getName() 返回的是全路径(如 C:\Users\test\file.txt),所以要用 new File().getName() 提取文件名
  • item.write() 会自动处理临时文件的移动,无需手动操作

4. 运行示例

将项目打包为 .war 文件,部署到 Tomcat(如 webapps/ROOT.war),启动后访问:

http://localhost:8080/

你会看到上传页面:

choosefile

选择文件并上传后,显示成功提示:

filesuccess

最后,检查服务器上的 uploads 目录,确认文件已保存:

imagesaved


5. 总结

方式 适用场景 优点 缺点
@MultipartConfig Servlet 3.0+ 原生支持,零依赖,代码简洁 老项目不兼容
Commons FileUpload Servlet < 3.0 兼容性好,功能稳定 需引入额外依赖

✅ 建议:新项目一律使用 @MultipartConfig,避免引入不必要的第三方库。
❌ 避免混用两种方式,容易导致请求解析冲突。

完整示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/web-modules/javax-servlets


原始标题:Uploading Files with Servlets and JSP

» 下一篇: Kotlin教程