杰瑞科技汇

Java Servlet文件上传如何实现?

文件上传在 Web 开发中非常常见,例如用户上传头像、附件等,在 Servlet 3.0 之前,处理文件上传非常繁琐,需要自己解析复杂的 multipart/form-data 格式,但从 Servlet 3.0 开始,官方提供了强大的 Part API,让文件上传变得异常简单。

Java Servlet文件上传如何实现?-图1
(图片来源网络,侵删)

下面我将分步讲解,从核心概念到完整代码示例。


核心概念:multipart/form-data

当你的 HTML 表单中包含文件输入框 (<input type="file">) 时,必须将表单的 enctype 属性设置为 multipart/form-data

  • application/x-www-form-urlencoded: 默认的编码方式,它只能处理文本数据,无法处理文件。
  • multipart/form-data: 这是一种特殊的编码类型,它可以将表单中的所有部分(包括文本字段和文件)分割成多个“部分”(Part)进行上传,每个部分都有自己的边界(Boundary),服务器可以根据这个边界来解析出不同的数据。

Servlet 3.0+ 的 Part API

Servlet 3.0 引入了 javax.servlet.http.Part 接口,它代表上传文件中的一个“部分”,通过这个 API,你可以轻松地获取文件信息、读取文件内容以及保存文件。

Part 接口的主要方法:

Java Servlet文件上传如何实现?-图2
(图片来源网络,侵删)
  • String getContentType(): 获取上传文件的 Content-Type(image/jpeg)。
  • String getName(): 获取表单中对应字段的 name 属性值。
  • String getSubmittedFileName(): 获取客户端原始的文件名,这是最常用的方法之一。
  • long getSize(): 获取上传文件的大小(字节)。
  • InputStream getInputStream(): 获取一个输入流,用于读取文件的内容。
  • void write(String path): 将上传的文件内容直接写入到服务器指定的路径,这是最简单、最高效的保存文件的方法。
  • void delete(): 当临时文件不再需要时,调用此方法将其删除。

实现步骤

我们将创建一个完整的文件上传示例,包含前端 JSP 页面和后端 Servlet。

第 1步:配置 web.xml (可选)

虽然现代 Web 应用(特别是使用注解的)通常不需要 web.xml,但如果你使用的是较老的项目或者想配置 Servlet 的 <multipart-config>web.xml 是一个标准的地方。

<!-- web.xml -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>FileUploadServlet</servlet-name>
        <servlet-class>com.example.FileUploadServlet</servlet-class>
        <!-- 配置文件上传的相关参数 -->
        <multipart-config>
            <!-- 临时文件存放的目录,默认是系统的临时目录 -->
            <location>/tmp</location>
            <!-- 上传文件总大小的最大值 (e.g., 5MB) -->
            <max-file-size>5242880</max-file-size>
            <!-- 请求中所有文件总大小的最大值 (e.g., 20MB) -->
            <max-request-size>20971520</max-request-size>
            <!-- 当文件大小超过 max-file-size 时,写入临时文件前的缓冲大小 (e.g., 1MB) -->
            <file-size-threshold>1048576</file-size-threshold>
        </multipart-config>
    </servlet>
    <servlet-mapping>
        <servlet-name>FileUploadServlet</servlet-name>
        <url-pattern>/upload</url-pattern>
    </servlet-mapping>
</web-app>

注意<multipart-config> 也可以直接在 Servlet 类上使用 @MultipartConfig 注解来配置,这更现代。

第 2步:创建前端上传页面 (upload.jsp)

这个页面包含一个文件输入框和一个提交按钮。

Java Servlet文件上传如何实现?-图3
(图片来源网络,侵删)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>文件上传</title>
</head>
<body>
    <h1>选择文件上传</h1>
    <form action="upload" method="post" enctype="multipart/form-data">
        <label for="file">选择文件:</label>
        <input type="file" id="file" name="file" required>
        <br><br>
        <input type="submit" value="上传">
    </form>
</body>
</html>

关键点

  • action="upload": 指向 Servlet 的 URL 映射。
  • method="post": 文件上传必须使用 POST 方法。
  • enctype="multipart/form-data": 必须设置,否则文件无法上传。

第 3步:创建后端 Servlet (FileUploadServlet.java)

这是处理文件上传的核心逻辑。

package com.example;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Paths;
// 使用注解来配置 multipart,它会覆盖 web.xml 中的配置(如果存在)
@WebServlet("/upload")
@MultipartConfig(
    fileSizeThreshold = 1024 * 1024 * 2,    // 2 MB
    maxFileSize = 1024 * 1024 * 10,         // 10 MB
    maxRequestSize = 1024 * 1024 * 50      // 50 MB
)
public class FileUploadServlet extends HttpServlet {
    // 定义在服务器上保存上传文件的目录
    private static final String UPLOAD_DIR = "uploads";
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        // 2. 获取上传文件的目录
        // getServletContext().getRealPath() 获取 Web 应用在服务器上的真实路径
        String applicationPath = getServletContext().getRealPath("");
        String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR;
        // 如果上传目录不存在,则创建
        File uploadFileDir = new File(uploadFilePath);
        if (!uploadFileDir.exists()) {
            uploadFileDir.mkdirs();
        }
        // 3. 处理上传的文件
        // request.getParts() 获取所有上传的 Part
        for (Part part : request.getParts()) {
            // 过滤掉不是文件上传的普通表单字段(例如文本输入)
            // Part 是一个文件,它会有一个 Content-Disposition 头
            if (part.getContentType() != null) {
                String fileName = Paths.get(part.getSubmittedFileName()).getFileName().toString();
                out.println("正在上传文件: " + fileName + "<br>");
                // 4. 将文件写入服务器
                // part.write() 是最简单的方法,它会将文件内容写入到指定的路径
                part.write(uploadFilePath + File.separator + fileName);
                out.println("文件已成功保存到: " + uploadFilePath + File.separator + fileName + "<br>");
            }
        }
        out.println("<br><a href='upload.jsp'>返回上传页面</a>");
    }
}

代码解析

  1. @WebServlet("/upload"): 将 Servlet 映射到 /upload URL。
  2. @MultipartConfig: 这个注解是关键,它告诉容器这个 Servlet 需要处理 multipart 请求,里面的参数配置了文件上传的大小限制等,与 web.xml 中的 <multipart-config> 作用相同。
  3. UPLOAD_DIR: 定义了一个相对路径 uploads,用于存放上传的文件,我们使用 getServletContext().getRealPath("") 来获取 Web 应用的根目录在服务器上的绝对路径,然后拼接上 uploads,得到最终的存储路径。
  4. request.getParts(): 获取请求中所有的 Part 对象,一个 Part 可以是一个文件,也可以是一个普通的表单字段。
  5. part.getContentType() != null: 这是一个简单的判断,用来区分文件 Part 和普通文本 Part,文件 Part 有 Content-Type,而普通文本 Part 没有。
  6. part.getSubmittedFileName(): 获取用户在客户端选择的原始文件名。
  7. part.write(...): 这是保存文件的核心方法,它将上传的文件内容直接写入到指定的服务器路径,这个方法会处理文件流的读写,非常方便。

运行和测试

  1. 部署项目: 将你的 Web 应用(包含 upload.jsp, FileUploadServlet.class 等)部署到 Tomcat 服务器上。
  2. 启动服务器: 启动 Tomcat。
  3. 访问页面: 在浏览器中访问 http://localhost:8080/你的项目名/upload.jsp
  4. 选择并上传: 选择一个文件,点击“上传”按钮。
  5. 检查结果:
    • 页面会显示上传成功的消息。
    • 在你的 Tomcat 部署目录下(apache-tomcat-9.0.x/webapps/你的项目名/),你会发现多了一个 uploads 文件夹,里面就是你上传的文件。

高级处理与注意事项

防止文件名冲突和恶意文件

上面的示例很简单,但在生产环境中,直接使用用户提供的文件名是不安全的。

  • 文件名冲突: 如果多个用户上传同名文件,后一个会覆盖前一个。
  • 安全风险: 恶意用户可能上传 .jsp, .exe, .php 等脚本文件,如果服务器配置不当,可能会导致代码执行风险。

改进建议

  • 生成唯一文件名: 使用 UUID (Universally Unique Identifier) 来生成一个唯一的文件名。

    import java.util.UUID;
    // ...
    String originalFileName = part.getSubmittedFileName();
    String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
    String uniqueFileName = UUID.randomUUID().toString() + fileExtension;
    // ...
    part.write(uploadFilePath + File.separator + uniqueFileName);
  • 限制文件类型: 检查文件的 Content-Type 或者文件扩展名,只允许上传特定类型的文件。

    String contentType = part.getContentType();
    if (contentType != null && (contentType.startsWith("image/jpeg") || contentType.startsWith("image/png"))) {
        // 允许上传
    } else {
        // 拒绝上传,并返回错误信息
        out.println("错误:只允许上传 JPG 或 PNG 图片!");
        return;
    }
  • 限制文件扩展名: 更严格的做法是检查文件扩展名。

    String allowedFileExtensions = ".jpg,.jpeg,.png,.gif";
    String originalFileName = part.getSubmittedFileName();
    String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")).toLowerCase();
    if (!allowedFileExtensions.contains(fileExtension)) {
        out.println("错误:不允许的文件类型!");
        return;
    }

手动处理文件流(不使用 part.write()

虽然 part.write() 很方便,但有时你可能需要更精细的控制,比如将文件保存到数据库或进行内存处理,这时可以使用 getInputStream()

// 在 for 循环内
if (part.getContentType() != null) {
    try (InputStream fileContent = part.getInputStream();
         FileOutputStream outputStream = new FileOutputStream(new File(uploadFilePath, uniqueFileName))) {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = fileContent.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        out.println("文件已成功保存到: " + uploadFilePath + File.separator + uniqueFileName + "<br>");
    } catch (IOException e) {
        e.printStackTrace();
        out.println("文件保存失败: " + e.getMessage());
    }
}

处理 Java Servlet 文件上传的最佳实践是:

  1. 使用 Servlet 3.0+ 的 @MultipartConfig 注解,配置好文件大小限制。
  2. 在前端 JSP 中,表单必须设置 method="post"enctype="multipart/form-data"
  3. 在后端 Servlet 中,通过 request.getParts() 获取所有上传部分。
  4. 通过 getContentType() 区分文件和普通字段
  5. 使用 getSubmittedFileName() 获取原始文件名,并强烈建议使用 UUID 生成唯一文件名以避免冲突和安全问题。
  6. 使用 part.write() 或手动流操作 将文件保存到服务器的安全目录中。
分享:
扫描分享到社交APP
上一篇
下一篇