【SpringMVC笔记】 - 8 - 文件上传与下载

【SpringMVC笔记】 - 8 - 文件上传与下载

一、文件上传

1. 核心概述

文件上传是 Web 开发中常见的功能,SpringMVC 针对文件上传提供了便捷的支持。在 SpringMVC 6 版本中,文件上传的配置方式与 Spring5 有显著区别:Spring6 移除了CommonsMultipartResolver类,不再依赖commons-fileupload依赖包,而是通过在web.xml中为 DispatcherServlet 配置multipart-config节点实现文件上传的参数控制。

2. 环境准备与核心依赖

SpringMVC 6 实现文件上传无需额外添加如下依赖(Spring5 需要):

xml 复制代码
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

仅需保证 SpringMVC 6 核心依赖正常引入即可。

3. 前端页面编写

文件上传的前端表单需满足三个核心要求:

  • 请求方式为 POST
  • 表单编码类型enctype="multipart/form-data"(默认application/x-www-form-urlencoded无法传输文件二进制数据)
  • 文件选择组件type="file",并指定name属性(后端通过该属性接收文件)

示例代码:

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    <!--文件上传表单-->
    <form th:action="@{/file/up}" method="post" enctype="multipart/form-data">
        文件:<input type="file" name="fileName"/><br>
        <input type="submit" value="上传">
    </form>
</body>
</html>

4. web.xml 配置(核心)

在 SpringMVC 6 中,需在 DispatcherServlet 的配置中添加multipart-config节点,用于控制文件上传的大小限制:

xml 复制代码
<!--前端控制器-->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!-- 服务器启动时初始化DispatcherServlet,提升首次请求效率 -->
    <load-on-startup>1</load-on-startup>
    <!-- 文件上传配置 -->
    <multipart-config>
        <!-- 单个文件最大大小:单位字节(示例为100KB) -->
        <max-file-size>102400</max-file-size>
        <!-- 整个表单所有文件总大小:单位字节(示例为100KB) -->
        <max-request-size>102400</max-request-size>
        <!-- 文件大小阈值,超过该值将写入临时文件,0表示始终写入磁盘 -->
        <file-size-threshold>0</file-size-threshold>
    </multipart-config>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

注意:Spring5 中需在 springmvc.xml 中配置CommonsMultipartResolver,Spring6 已废弃该类,统一使用multipart-config配置。

5. SpringMVC 核心配置(springmvc.xml)

基础的 SpringMVC 配置需保证组件扫描、视图解析器、注解驱动等核心配置生效,示例:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd 
                           http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 组件扫描:扫描Controller所在包 -->
    <context:component-scan base-package="com.zzz.controller"/>

    <!-- 配置Thymeleaf视图解析器 -->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <property name="characterEncoding" value="UTF-8"/>
        <property name="order" value="1"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!-- 视图控制器:访问/直接跳转index页面 -->
    <mvc:view-controller path="/" view-name="index"/>

    <!-- 静态资源处理 -->
    <mvc:default-servlet-handler/>

    <!-- 开启注解驱动 -->
    <mvc:annotation-driven/>
</beans>

6. Controller 编写(核心业务逻辑)

文件上传的核心逻辑是接收前端传递的MultipartFile对象,将文件流写入服务器指定目录,关键步骤:

  1. 接收MultipartFile参数(通过@RequestParam绑定前端name属性);
  2. 获取文件原始名称,生成唯一文件名(避免文件覆盖);
  3. 获取服务器存储目录,不存在则创建;
  4. 通过输入流读取文件、输出流写入服务器目录;
  5. 关闭流资源,返回跳转页面。

示例代码:

java 复制代码
package com.zzz.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.UUID;

@Controller
public class FileController {

    /**
     * 文件上传接口
     * @param multipartFile 前端传递的文件对象(name="fileName")
     * @param request 请求对象,用于获取服务器真实路径
     * @return 跳转成功页面
     * @throws IOException 流操作异常
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public String upload(@RequestParam("filename") MultipartFile multipartFile,
                         HttpServletRequest request) throws IOException {
        // 1. 获取请求参数名(前端input的name属性值)
        String paramName = multipartFile.getName();
        System.out.println("请求参数名:" + paramName);

        // 2. 获取文件原始名称(如:test.png)
        String originalFilename = multipartFile.getOriginalFilename();
        System.out.println("文件原始名称:" + originalFilename);

        // 3. 生成唯一文件名:UUID + 文件后缀(避免文件覆盖)
        String fileSuffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        String uniqueFileName = UUID.randomUUID().toString() + fileSuffix;

        // 4. 获取服务器存储目录(WEB-INF/upload,不存在则创建)
        String realPath = request.getServletContext().getRealPath("/upload");
        File uploadDir = new File(realPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs(); // 多级目录创建
        }

        // 5. 构建目标文件对象
        File destFile = new File(uploadDir.getAbsolutePath() + "/" + uniqueFileName);

        // 6. 读取文件流并写入服务器
        // 输入流(读取前端上传的文件)
        InputStream inputStream = multipartFile.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        // 输出流(写入服务器目录)
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));

        // 7. 缓冲区读写(提高IO效率)
        byte[] buffer = new byte[1024]; // 1KB缓冲区
        int len; // 每次读取的字节数
        while ((len = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }

        // 8. 资源释放
        bos.flush(); // 刷新缓冲区
        bos.close();
        bis.close();
        inputStream.close();

        // 9. 跳转成功页面
        return "ok";
    }
}

7. 关键注意事项

  • 文件名唯一性:必须使用 UUID / 时间戳等生成唯一文件名,否则同名文件会被覆盖;
  • 流资源释放:必须关闭输入 / 输出流,避免资源泄漏;
  • 文件大小限制multipart-config中的大小单位为字节,需根据业务调整;
  • 编码问题:配置字符编码过滤器,避免文件名中文乱码:
xml 复制代码
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

二、文件下载

1. 核心概述

文件下载的核心是通过ResponseEntity<byte[]>封装文件字节数组、响应头信息,SpringMVC 会自动将该对象转换为下载响应,关键是设置正确的响应头(文件类型、下载文件名)。

2. 前端页面编写

通过超链接触发下载请求,示例:

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
<h1>文件上传与下载</h1>
<hr>
<!--文件上传表单-->
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
    文件 :<input type="file" name="filename">
    <input type="submit" value="上传">
</form>
<hr>
<!--文件下载链接-->
<a th:href="@{/download}">下载</a>
</body>
</html>

3. Controller 编写(下载核心逻辑)

下载逻辑关键步骤:

  1. 定位服务器上的目标文件;
  2. 创建HttpHeaders,设置响应类型为二进制流(APPLICATION_OCTET_STREAM);
  3. 设置Content-Disposition响应头,指定下载文件名;
  4. 读取文件字节数组,封装为ResponseEntity返回。

示例代码:

java 复制代码
package com.zzz.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

@Controller
public class FileController {

    /**
     * 文件下载接口
     * @param request 请求对象,用于获取服务器文件路径
     * @return ResponseEntity封装的文件字节数组与响应头
     * @throws IOException 文件读取异常
     */
    @GetMapping("/download")
    public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException {
        // 1. 定位服务器上的目标文件(示例:upload目录下的OIP.png)
        String filePath = request.getServletContext().getRealPath("/upload/OIP.png");
        File file = new File(filePath);
        if (!file.exists()) {
            // 可选:文件不存在时返回404
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        // 2. 构建响应头
        HttpHeaders headers = new HttpHeaders();
        // 设置响应内容类型:二进制流(通用文件类型)
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 设置下载文件名(attachment表示附件下载,inline表示在线预览)
        headers.setContentDispositionFormData("attachment", file.getName());

        // 3. 读取文件字节数组,封装ResponseEntity返回
        byte[] fileBytes = Files.readAllBytes(file.toPath());
        return new ResponseEntity<>(fileBytes, headers, HttpStatus.OK);
    }
}

4. 关键注意事项

  • 响应头配置:
    • ContentType设置为APPLICATION_OCTET_STREAM,表示二进制流,兼容所有文件类型;
    • Content-Dispositionattachment表示以附件形式下载,若改为inline则在线预览(如图片、文本);
  • 文件路径校验 :必须校验文件是否存在,避免FileNotFoundException
  • 中文文件名乱码:若文件名包含中文,需对文件名进行 URL 编码:
java 复制代码
// 解决中文文件名乱码
String encodedFileName = java.net.URLEncoder.encode(file.getName(), "UTF-8");
headers.setContentDispositionFormData("attachment", encodedFileName);
  • 大文件下载优化 :若下载大文件,不建议使用Files.readAllBytes()(一次性加载到内存),可通过StreamingResponseBody实现流式下载:
java 复制代码
@GetMapping("/download/large")
public ResponseEntity<StreamingResponseBody> downloadLargeFile(HttpServletRequest request) throws FileNotFoundException {
    String filePath = request.getServletContext().getRealPath("/upload/largeFile.zip");
    File file = new File(filePath);
    if (!file.exists()) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    headers.setContentDispositionFormData("attachment", java.net.URLEncoder.encode(file.getName(), "UTF-8"));

    // 流式下载
    StreamingResponseBody responseBody = outputStream -> {
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
            int len;
            while ((len = bis.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    };

    return new ResponseEntity<>(responseBody, headers, HttpStatus.OK);
}

三、完整测试流程

1. 上传测试

  1. 启动 Web 服务器(如 Tomcat);
  2. 访问首页http://localhost:8080/,选择本地文件点击 "上传";
  3. 查看服务器WEB-INF/upload目录,确认文件已上传且文件名是 UUID 格式;
  4. 页面跳转至ok.html,显示 "OK" 表示上传成功。

2. 下载测试

  1. 点击页面中的 "下载" 链接;
  2. 浏览器弹出文件下载弹窗,确认文件名和大小正确;
  3. 下载完成后,校验文件完整性(如打开图片、解压压缩包等)。

四、常见问题排查

1. 上传文件为空

  • 检查前端表单enctype="multipart/form-data"是否配置;
  • 检查MultipartFile参数的@RequestParam名称与前端name属性是否一致;
  • 检查multipart-config的文件大小限制是否过小,导致文件被拦截。

2. 上传文件目录不存在

  • 确认使用mkdirs()(创建多级目录)而非mkdir()(仅创建单级目录);
  • 检查服务器目录权限,确保应用有写入权限。

3. 下载文件乱码

  • 对下载文件名进行 URL 编码;
  • 检查字符编码过滤器是否配置生效。

4. Spring6 与 Spring5 兼容问题

  • Spring5 需配置CommonsMultipartResolver,Spring6 需配置multipart-config
  • Spring6 使用 Jakarta EE API(jakarta.servlet),Spring5 使用 Java EE API(javax.servlet),注意包导入。

五、总结

  1. SpringMVC 6 文件上传无需依赖commons-fileupload,通过web.xml的multipart-config配置文件大小限制;
  2. 文件上传核心是处理MultipartFile对象,通过 IO 流将文件写入服务器,需保证文件名唯一;
  3. 文件下载核心是通过ResponseEntity<byte[]>封装文件字节和响应头,支持附件下载 / 在线预览;
  4. 开发中需注意资源释放、编码问题、文件路径校验,大文件下载建议使用流式传输。
相关推荐
额呃呃2 小时前
Andriod项目番茄钟
java·开发语言
梅孔立2 小时前
Java 基于 POI 模板 Excel 导出工具类 双数据源 + 自动合并单元格 + 自适应行高 完整实战
java·开发语言·excel
dLYG DUMS2 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Huangxy__2 小时前
java相机手搓(后续是文件保存以及接入大模型)
java·开发语言·数码相机
MY_TEUCK3 小时前
从零开始:使用Sealos Devbox快速搭建云原生开发环境
人工智能·spring boot·ai·云原生·aigc
摇滚侠3 小时前
Java Map 类型的数据可以存储到 Redis Hash 类型中
java·redis·哈希算法
人道领域3 小时前
【LeetCode刷题日记】:151翻转字符串的单词(两种解法)
java·开发语言·算法·leetcode·面试
lifallen3 小时前
Flink 深度解析:从 TM、Task、Operator、UDF 到 Mailbox 与 OperatorChain
java·大数据·flink
Seven973 小时前
【从0到1构建一个ClaudeAgent】协作-Worktree+任务隔离
java