java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JavaWeb文件上传方案

JavaWeb实现文件上传的四种方案

作者:CarIise

还在纠结Java文件上传用哪种方式,本文对比Apache Commons FileUpload、Servlet3.0 Part、Spring MVC和Spring Boot四种主流方案,从依赖配置到核心代码,再到易错点速查,帮你快速选择最适合项目的上传实现,避开文件大小限制、路径穿越等常见坑,需要的朋友可以参考下

第一部分:方式一 —— Apache Commons FileUpload(原生 Servlet 第三方方案)

1.1 方案概述与特点

老师强调:这是面向 Servlet 2.5 及更早时代的方案。Java 标准库未内置 multipart 解析,必须引入第三方依赖手动解析 HttpServletRequest。

1.2 导入依赖

需要两个依赖,缺一不可:

<!-- 文件上传解析核心 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>
<!-- IO工具,FileItem.write()内部依赖它 -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

1.3 页面设计

核心强调:表单必须满足两个条件:

  1. enctype="multipart/form-data"(否则文件内容不传输)。
  2. <input type="file" name="...">(name 对应后端字段名)。
<form action="uploadServlet" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="uploadFile" />
    <input type="submit" value="上传" />
</form>

1.4 接口逻辑实现

核心组件:

@WebServlet("/uploadServlet")
public class FileUploadServlet extends HttpServlet {
    private static final String UPLOAD_DIRECTORY = "upload";
    private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3;  // 3MB
    private static final long MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB
    private static final long MAX_REQUEST_SIZE = 1024 * 1024 * 50; // 50MB
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        // 1. 检查是否为 multipart 请求
        if (!ServletFileUpload.isMultipartContent(request)) {
            response.getWriter().println("错误:表单必须包含 enctype=multipart/form-data");
            return;
        }
        // 2. 配置 DiskFileItemFactory
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
        // 3. 创建解析器并设置限制
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(MAX_FILE_SIZE);
        upload.setSizeMax(MAX_REQUEST_SIZE);
        upload.setHeaderEncoding("UTF-8");
        // 4. 构造上传目录
        String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        try {
            // 5. 解析请求,得到 FileItem 列表
            List<FileItem> formItems = upload.parseRequest(request);
            if (formItems != null && !formItems.isEmpty()) {
                for (FileItem item : formItems) {
                    // 6. 只处理文件字段,跳过普通表单字段
                    if (item.isFormField()) {
                        continue;
                    }
                    // 7. 安全提取文件名,防止路径穿越
                    String fileName = new File(item.getName()).getName();
                    if (fileName == null || fileName.isEmpty()) {
                        continue;
                    }
                    // 8. 写盘
                    File storeFile = new File(uploadPath, fileName);
                    item.write(storeFile);
                    response.getWriter().println("上传成功:" + storeFile.getAbsolutePath());
                }
            }
        } catch (Exception ex) {
            response.getWriter().println("上传失败:" + ex.getMessage());
        }
    }
}

第二部分:方式二 —— Servlet 3.0 原生 @MultipartConfig + Part

2.1 方案概述与特点

老师强调:规范内置 multipart 支持,零第三方依赖。在 Servlet 上标注 @MultipartConfig 后由容器自动解析请求。

2.2 核心变化

<!-- Servlet API,由容器提供 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

2.3 接口逻辑实现

核心组件:

@WebServlet("/partUploadServlet")
@MultipartConfig(
    fileSizeThreshold = 1024 * 1024 * 3, // 3MB
    maxFileSize = 1024 * 1024 * 40,      // 40MB
    maxRequestSize = 1024 * 1024 * 50    // 50MB
)
public class PartUploadServlet extends HttpServlet {
    private static final String UPLOAD_DIRECTORY = "upload";
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        // 1. 构造上传目录
        String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        try {
            // 2. 一次性取出所有 Part(文件字段 + 普通字段混在一起)
            Collection<Part> parts = request.getParts();
            for (Part part : parts) {
                // 3. 用 getSubmittedFileName() 是否为 null 区分普通字段
                String submittedName = part.getSubmittedFileName();
                if (submittedName == null) {
                    continue; // 普通表单字段
                }
                if (submittedName.isEmpty()) {
                    continue; // 用户未选择文件的空控件
                }
                // 4. 安全提取文件名
                String fileName = new File(submittedName).getName();
                // 5. 用 UUID 重命名,避免同名覆盖
                String ext = "";
                int dot = fileName.lastIndexOf('.');
                if (dot > 0) {
                    ext = fileName.substring(dot);
                }
                String savedName = UUID.randomUUID().toString().replace("-", "") + ext;
                // 6. 流式写盘(推荐,比 Part.write 路径语义更明确)
                File target = new File(uploadPath, savedName);
                try (InputStream in = part.getInputStream()) {
                    Files.copy(in, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
                }
                // 7. 清理临时文件
                part.delete();
                response.getWriter().println("上传成功:" + target.getAbsolutePath() 
                    + "(原始文件名:" + fileName + ",大小:" + part.getSize() + "字节)");
            }
        } catch (IllegalStateException e) {
            response.getWriter().println("上传失败:文件超过大小限制- " + e.getMessage());
        } catch (ServletException e) {
            response.getWriter().println("上传失败:请求解析异常- " + e.getMessage());
        }
    }
}

2.4 方式一 vs 方式二 核心差异对比

维度方式一:Commons FileUpload方式二:Servlet 3.0 Part
依赖需 commons-fileupload + commons-io零第三方依赖,仅 Servlet API
声明无需注解,代码手动解析必须 @MultipartConfig,容器负责解析
解析入口ServletFileUpload.parseRequest(request)request.getParts()
字段区分FileItem.isFormField()Part.getSubmittedFileName() == null
写盘item.write(File)推荐 getInputStream() + Files.copy()
大小限制代码设置 setFileSizeMax@MultipartConfig 注解属性(声明式)
异常SizeException / FileUploadExceptionIllegalStateException / ServletException
临时文件DiskFileItemFactory + item.delete()@MultipartConfig.location + part.delete()
容器要求兼容 Servlet 2.5必须 Servlet 3.0+

第三部分:方式三 —— Spring MVC MultipartResolver + MultipartFile

3.1 方案概述与特点

老师强调:这是 Spring MVC 对 Servlet multipart 的声明式封装。MultipartResolver 将 HttpServletRequest 包装为 MultipartHttpServletRequest,控制器方法直接声明 MultipartFile 参数即可。

3.2 导入依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

3.3 配置多媒体解析器(springmvc.xml)

核心强调:id 的名称必须是 multipartResolver,因为前端控制器 DispatcherServlet 是按这个名称去 IOC 容器中找解析器的。

<!-- 配置文件上传的解析器组件。id的名称固定,不能乱写 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置上传文件总大小 8M = 8 * 1024 * 1024 -->
    <property name="maxUploadSize" value="8388608" />
</bean>

3.4 接口逻辑实现

核心强调:MultipartFile 的变量名必须和表单 file 标签的 name 属性一致。

@Controller
public class UploadController {
    @RequestMapping("/fileupload.do")
    public String upload(MultipartFile upload, HttpServletRequest request) throws IOException {
        // 1. 获取服务器物理路径
        String realPath = request.getSession().getServletContext().getRealPath("/uploads");
        // 2. 创建文件夹
        File file = new File(realPath);
        if(!file.exists()){
            file.mkdirs();
        }
        // 3. 获取原始文件名并生成唯一UUID前缀
        String filename = upload.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
        filename = uuid + "_" + filename;
        // 4. 将文件写入磁盘
        upload.transferTo(new File(file, filename));
        return "suc";
    }
}

第四部分:方式四 —— Spring Boot + MultipartFile

4.1 方案概述与特点

老师强调:与方式三核心代码几乎一样,真正的区别在于**"零配置"**。Spring Boot 自动注册了 StandardServletMultipartResolver,无需手写 Bean。

4.2 导入依赖

只需 spring-boot-starter-web,它已内置 Spring MVC、Tomcat 和 Jackson,无需额外引包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

4.3 配置上传限制(关键步骤)

核心强调:Spring Boot 默认限制极小(单文件 1MB,总请求 10MB)。必须根据需求在 application.yml 中调整,否则上传稍大文件直接报 MaxUploadSizeExceededException。

spring:
  servlet:
    multipart:
      max-file-size: 10MB       # 单个文件上限
      max-request-size: 50MB    # 整个请求上限
      file-size-threshold: 2MB  # 超过2MB才写临时磁盘,默认0

4.4 接口逻辑实现

@RestController
public class FileUploadController {
    @Value("${file.upload-dir:uploads}")
    private String uploadDir;
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) throws IOException {
        // 1. 空文件校验
        if (file == null || file.isEmpty()) {
            return "请选择要上传的文件";
        }
        // 2. 构造上传目录(绝对路径)
        File dir = new File(uploadDir).getAbsoluteFile();
        if (!dir.exists()) {
            dir.mkdirs();
        }
        // 3. 安全提取文件名
        String originalName = file.getOriginalFilename();
        if (originalName == null || originalName.isEmpty()) {
            return "文件名无效";
        }
        originalName = new File(originalName).getName();
        // 4. UUID 重命名
        String ext = "";
        int dot = originalName.lastIndexOf('.');
        if (dot > 0) {
            ext = originalName.substring(dot);
        }
        String savedName = UUID.randomUUID().toString().replace("-", "") + ext;
        // 5. transferTo 写盘(必须传绝对路径)
        File target = new File(dir, savedName);
        file.transferTo(target);
        return "上传成功:" + target.getAbsolutePath() 
            + "(原始文件名:" + originalName + ",大小:" + file.getSize() + "字节)";
    }
    // 6. 统一处理超限异常
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public String handleMaxSize(MaxUploadSizeExceededException e) {
        return "上传失败:文件超过大小限制,请检查 spring.servlet.multipart 配置";
    }
}

附录:核心易错点速查表

类别易错点正确理解
表单漏写 enctype必须是 multipart/form-data,否则传的是普通文本
请求方式用 GET 请求上传文件上传必须用 POST 请求
参数名表单 name 与形参名不一致MultipartFile 变量名必须和前端 name 保持一致
方式一忘记 commons-ioFileItem.write() 底层依赖它,二者缺一不可
方式二忘记加 @MultipartConfig不加会直接抛 IllegalStateException
方式二直接调用 Part.write(path)路径语义容易出错,推荐 getInputStream + Files.copy
方式三解析器 id 乱起Spring MVC 配置中 id="multipartResolver" 固定不可改
方式三忘记配置解析器会导致 MultipartFile 接收不到数据,不报错但为 null
方式四使用默认大小限制Spring Boot 默认单文件仅 1MB,稍大文件会报错
通用用原文件名保存必须用 UUID 或时间戳重命名,防止同名覆盖
通用信任前端传的文件名必须用 new File(name).getName() 处理,防止路径穿越(如 ../)
通用没有大小限制前后端都要做限制,防止超大文件撑爆内存或磁盘

以上就是JavaWeb实现文件上传的四种方案的详细内容,更多关于JavaWeb文件上传方案的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文