杰瑞科技汇

Spring如何实现文件上传?

核心概念

无论使用哪种方式,文件上传的原理基本相同:

Spring如何实现文件上传?-图1
(图片来源网络,侵删)
  1. 前端: 在 HTML 表单中,必须设置 enctype="multipart/form-data",并且使用 <input type="file">
  2. 后端:
    • Spring 框架(通过 CommonsMultipartResolverStandardServletMultipartResolver)会检测到这是一个文件上传请求。
    • 它会将请求体中的普通字段和文件内容解析出来。
    • 会被封装在一个 MultipartFile 对象中。
    • 我们在 Controller 中接收这个 MultipartFile 对象,然后就可以读取文件内容、获取文件名、文件大小等信息,并将其保存到服务器的指定路径。

Spring Boot (推荐)

Spring Boot极大地简化了文件上传的配置,开箱即用。

项目依赖

确保你的 pom.xml 中包含了 spring-boot-starter-web 依赖,它已经包含了所有必需的库。

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

配置文件

application.propertiesapplication.yml 中,你可以配置上传文件的一些参数,例如最大文件大小、最大请求大小等。

application.properties 示例:

# 单个文件最大大小 (10MB)
spring.servlet.multipart.max-file-size=10MB
# 整个请求最大大小 (包含多个文件和表单数据) (11MB)
spring.servlet.multipart.max-request-size=11MB
# 临时文件存放目录 (可选)
# spring.servlet.multipart.location=/tmp

application.yml 示例:

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 11MB

创建 Controller

创建一个 Controller 来处理文件上传请求。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class FileUploadController {
    // 定义一个固定的上传目录,在实际项目中最好从配置中读取
    private static final String UPLOADED_FOLDER = "/path/to/your/upload/directory/";
    @PostMapping("/upload")
    public @ResponseBody String handleFileUpload(@RequestParam("file") MultipartFile file,
                                                RedirectAttributes redirectAttributes) {
        if (file.isEmpty()) {
            return "请选择一个文件上传";
        }
        try {
            // 获取原始文件名
            String originalFilename = file.getOriginalFilename();
            // 构建文件保存路径
            Path destination = Paths.get(UPLOADED_FOLDER + originalFilename);
            // 将文件保存到指定路径
            Files.copy(file.getInputStream(), destination);
            return "文件上传成功: " + originalFilename;
        } catch (IOException e) {
            e.printStackTrace();
            return "文件上传失败: " + e.getMessage();
        }
    }
}

代码解释:

  • @Controller: 声明这是一个 Spring MVC 的控制器。
  • @PostMapping("/upload"): 映射 HTTP POST 请求到 /upload 路径。
  • @RequestParam("file") MultipartFile file:
    • @RequestParam("file") 将 HTTP 请求中名为 file 的参数绑定到方法参数 file 上,这个 file 必须和 HTML 表单中 <input name="file">name 属性一致。
    • MultipartFile 是 Spring 提供的接口,用于封装上传的文件信息。
  • file.isEmpty(): 检查文件是否为空。
  • file.getOriginalFilename(): 获取客户端上传的原始文件名。
  • file.getInputStream(): 获取文件的输入流。
  • Files.copy(...): Java NIO 提供的高效文件复制方法,将输入流中的内容写入到目标路径。
  • @ResponseBody: 将方法的返回值直接作为 HTTP 响应体返回。

创建 HTML 表单

src/main/resources/templates/ 目录下创建一个 upload.html 文件(如果你使用 Thymeleaf 等模板引擎)。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>文件上传</title>
</head>
<body>
    <h1>文件上传示例</h1>
    <form method="POST" action="/upload" enctype="multipart/form-data">
        <input type="file" name="file" />
        <button type="submit">上传</button>
    </form>
</body>
</html>

关键点:

  • method="POST": 文件上传必须使用 POST 方法。
  • action="/upload": 指向后端处理上传的 Controller 路径。
  • enctype="multipart/form-data": 这是必须的,它告诉浏览器要以二进制流的方式提交表单数据,才能正确上传文件。

传统 Spring MVC (非 Boot)

如果你使用的是传统的 Spring 项目,配置会稍微多一些。

项目依赖

需要添加 commons-fileuploadcommons-io 依赖。

<dependencies>
    <!-- Spring MVC 核心依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!-- 文件上传依赖 -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

配置 web.xmlDispatcherServlet

确保 web.xml 中配置了 DispatcherServlet

<web-app ...>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

配置 Spring MVC 配置文件 (dispatcher-servlet.xml)

这是最关键的一步,需要配置 MultipartResolver

<?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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 启用注解驱动 -->
    <mvc:annotation-driven/>
    <!-- 扫描 Controller -->
    <context:component-scan base-package="com.yourpackage"/>
    <!-- 配置 MultipartResolver -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 设置上传文件的最大尺寸,单位是字节 (10MB) -->
        <property name="maxUploadSize" value="10485760"/>
        <!-- 设置请求的最大尺寸 (11MB) -->
        <property name="maxInMemorySize" value="11534336"/>
        <!-- 设置默认编码 -->
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

关键点:

  • CommonsMultipartResolver: 这是 Spring MVC 中最常用的 MultipartResolver 实现。
  • maxUploadSize: 设置单个请求的最大允许大小,防止用户上传超大文件耗尽服务器资源。
  • defaultEncoding: 设置文件名和内容的默认编码,防止中文乱码。

Controller 和 HTML 表单

Controller 的代码和 Spring Boot 中的几乎完全一样,只是返回的视图名不同,HTML 表单也完全相同。

// Controller 代码 (与 Spring Boot 版本几乎一样)
@Controller
public class FileUploadController {
    private static final String UPLOADED_FOLDER = "/path/to/your/upload/directory/";
    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file,
                                   Model model) {
        if (file.isEmpty()) {
            model.addAttribute("message", "请选择一个文件上传");
            return "uploadStatus"; // 返回一个视图
        }
        try {
            byte[] bytes = file.getBytes();
            Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
            Files.write(path, bytes);
            model.addAttribute("message", "文件上传成功: " + file.getOriginalFilename());
            return "uploadStatus";
        } catch (IOException e) {
            e.printStackTrace();
            model.addAttribute("message", "文件上传失败: " + e.getMessage());
            return "uploadStatus";
        }
    }
}

进阶主题

文件名处理(防止覆盖和乱码)

直接使用 file.getOriginalFilename() 可能会有问题:

  • 文件名冲突: 如果多个用户上传同名文件,后一个会覆盖前一个。
  • 安全问题: 恶意用户可能上传带有 的文件名进行路径遍历攻击。
  • 中文乱码: 不同浏览器编码方式不同,可能导致文件名乱码。

推荐做法:

import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
// ...
@PostMapping("/upload-safe")
public @ResponseBody String handleFileUploadSafe(@RequestParam("file") MultipartFile file) throws IOException {
    if (file.isEmpty()) {
        return "请选择一个文件上传";
    }
    // 1. 获取原始文件名
    String originalFilename = file.getOriginalFilename();
    // 2. 使用 UUID 生成新的文件名,防止覆盖
    String extension = StringUtils.getFilenameExtension(originalFilename);
    String newFileName = UUID.randomUUID().toString();
    if (extension != null && !extension.isEmpty()) {
        newFileName += "." + extension;
    }
    // 3. 构建安全的保存路径 (防止路径遍历)
    // UPLOADED_FOLDER 应该是绝对路径,并且确保其安全性
    Path destination = Paths.get(UPLOADED_FOLDER).resolve(newFileName).normalize();
    // 确保目标路径在上传目录下
    if (!destination.startsWith(Paths.get(UPLOADED_FOLDER).normalize())) {
        throw new IOException("非法的文件路径");
    }
    // 4. 使用 NIO 的 copy 方法,它会自动处理文件创建和内容写入
    Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
    return "文件上传成功,新文件名: " + newFileName;
}

文件存储到云服务(如 AWS S3, 阿里云 OSS)

在实际生产环境中,通常不会将文件直接存储在服务器的本地磁盘上,而是使用对象存储服务。

基本流程是:

  1. 在 Controller 中接收 MultipartFile
  2. 使用云服务商的 SDK(如 AWS SDK for Java)。
  3. MultipartFile 获取 InputStream 和文件元数据(如 Content-Type)。
  4. 调用云服务的 API(s3Client.putObject())将输入流上传到云端。
  5. 返回文件在云端的 URL 给前端。

这样做的好处是高可用、可扩展、成本低,并且可以将应用服务器和存储服务器分离。

特性 Spring Boot 传统 Spring MVC
依赖 spring-boot-starter-web (自动包含) spring-webmvc, commons-fileupload
配置 application.properties / application.yml web.xml, dispatcher-servlet.xml (CommonsMultipartResolver)
核心类 MultipartFile MultipartFile
优点 简单、快速、配置少,是现代开发的首选 灵活,但配置繁琐,适合老项目或复杂需求

对于新项目,强烈推荐使用 Spring Boot,它能让你更专注于业务逻辑,而不是繁琐的配置。

分享:
扫描分享到社交APP
上一篇
下一篇