Skip to content

Spring MVC 文件上传

在实际的项目开发中,文件的上传和下载可以说是最常用的功能之一,例如图片的上传与下载、邮件附件的上传和下载等。本节我们将对 Spring MVC 中的文件上传功能进行讲解。

在 Spring MVC 中想要实现文件上传工作,需要的步骤如下。

1. 编写 form 表单

在 Spring MVC 项目中,大多数的文件上传功能都是通过 form 表单提交到后台服务器的。

form 表单想要具有文件上传功能,其必须满足以下 3 个条件。

  • form 表单的 method 属性必须设置为 post。
  • form 表单的 enctype 属性设置为 multipart/form-data。
  • 至少提供一个 type 属性为 file 的 input 输入框。

常见的文件上传表单示例代码如下。

html
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="fileName" multiple="multiple"/>
    <input type="submit" value="上传">
</form>

当 form 表单的 enctype 属性为 multipart/form-data 时,浏览器会以二进制流的方式对表单数据进行处理,由服务端对文件上传的请求进行解析和处理。

在上面的代码中,除了满足文件上传表单所必须具备的 3 个条件外, 标签中还增加了一个 multiple 属性。该属性可以让我们同时选择对个文件进行上传,即实现多文件上传功能。

2. 配置文件解析器(MultipartResolver )

Spring MVC 提供了一个名为 MultipartResolver 的文件解析器,来实现文件上传功能。MultipartResolver 本身是一个接口,我们需要通过它的实现类来完成对它的实例化工作。

MultipartResolver 接口共有两个实现类,如下表。

实现类说明依赖支持的 Servlet 版本
StandardServletMultipartResolver它是 Servlet 内置的上传功能不需要第三方 JAR 包的支持仅支持 Servlet 3.0 及以上版本
CommonsMultipartResolver借助 Apache 的 commons-fileupload 来完成具体的上传操作需要 Apache 的 commons-fileupload 等 JAR 包的支持不仅支持 Servlet 3.0 及以上版本,还可以在比较旧的 Servlet 版本中使用

以上这两个 MultipartResolver 的实现类,无论使用哪一个都可以实现 Spring MVC 的文件上传功能。这里,我们以 CommonsMultipartResolver 为例进行讲解。

想要在 Spring MVC 中使用 CommonsMultipartResolver 对象实现文件上传,我们需要在 Spring MVC 的配置文件中对其进行以下配置。

xml
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"></property>
    <property name="maxUploadSize" value="1024000"></property>
</bean>

在以上配置中,除了定义了 CommonsMultipartResolver 的 Bean 外,还通过 <property> 标签对文件的编码格式和上传文件的大小进行了配置。

通过 <property> 可以对 CommonsMultipartResolver 的多个属性进行配置,其中常用的属性如下表。

属性说明
defaultEncoding上传文件的默认编码格式。
maxUploadSize上传文件的最大长度(单位为字节)。
maxInMemorySize读取文件到内存中的最大字节数。
resolveLazily判断是否要延迟解析文件。

注意:当我们在 Spring MVC 的配置文件中对 CommonsMultipartResolver 的 Bean 进行定义时,必须指定这个 Bean 的 id 为 multipartResolver,否则就无法完成文件的解析和上传工作。

3. 引入 Jar 包

由于 CommonsMultipartResolver 是 Spring MVC 内部通过 Apache Commons FileUpload 技术实现的,因此我们还需要将 Apache Commons FileUpload 组件的相关依赖引入到项目中。

注:点击上面的链接,即可下载相应的 JAR 包。

4. 编写控制器方法

在完成上面的所有步骤后,接下来,我们只需要在 Controller 中编写文件上传的方法即可实现文件的上传。

java
@Controller
public class FileUploadController {
    @RequestMapping("/uplaod")
    public String upload(MultipartFile file) {
        if (!file.isEmpty()) {
            return "success";
        }
        return "error";
    }
}

在该控制器方法中包含一个 org.springframework.web.multipart.MultipartFile 接口类型的形参,该参数用来封装被上传文件的信息。MultipartFile 接口是 InputStreamSource 的子接口,该接口中提供了多个不同的方法,如下表。

名称作用
byte[] getBytes()以字节数组的形式返回文件的内容。
String getContentType()返回文件的内容类型。
InputStream getInputStream()返回一个 input 流,从中读取文件的内容。
String getName()返回请求参数的名称。
String getOriginalFillename()返回客户端提交的原始文件名称。
long getSize()返回文件的大小,单位为字节。
boolean isEmpty()判断被上传文件是否为空。
void transferTo(File destination)将上传文件保存到目标目录下。

上传文件示例

下面,我们就通过一个实例,来演示下如何在 Spring MVC 中上传文件,具体步骤如下。

  1. 新建一个名为 springmvc-file-demo 的 Web 工程,并将 Spring MVC 以及 Apache Commons FileUpload 相关的依赖导入到该工程中,web.xml 配置如下。
xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
    <!--请求和响应的字符串过滤器-->
    <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>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--来处理 PUT 和 DELETE 请求的过滤器-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
    <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>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  1. 在 src 目录下新建一个 Spring MVC 的配置文件 springMVC.xml,配置内容如下。
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">
    <!--开启组件扫描-->
    <context:component-scan base-package="net.biancheng.c"></context:component-scan>
    <!-- 配置 Thymeleaf 视图解析器 -->
    <bean id="viewResolver"
          class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 视图前缀 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!-- 视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
    <!--当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--配置文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置上传文件的默认编码格式-->
        <property name="defaultEncoding" value="UTF-8"></property>
        <!--设置允许上传的最大长度-->
        <property name="maxUploadSize" value="1024000"></property>
    </bean>
    <!--访问"/" 跳转到 file-upload.html 中-->
    <mvc:view-controller path="/" view-name="file-upload"></mvc:view-controller>
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
</beans>
  1. 在 net.biancheng.c.entity 包下,创建一个名为 Student 的类,代码如下。
java
package net.biancheng.c.entity;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public class Student {
    //学号
    private String stuId;
    //姓名
    private String stuName;
    //年龄
    private Integer age;
    //用于接收后台上传的文件
    private List<MultipartFile> photos;
    //文件名称的字符串
    private String fileNameStr;
    //已上传图片的路径集合
    private List<String> path;
    public List<String> getPath() {
        return path;
    }
    public void setPath(List<String> path) {
        this.path = path;
    }
    public void setPhotos(List<MultipartFile> photos) {
        this.photos = photos;
    }
    public String getFileNameStr() {
        return fileNameStr;
    }
    public void setFileNameStr(String fileNameStr) {
        this.fileNameStr = fileNameStr;
    }
    public String getStuId() {
        return stuId;
    }
    public void setStuId(String stuId) {
        this.stuId = stuId;
    }
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public List<MultipartFile> getPhotos() {
        return photos;
    }
    @Override
    public String toString() {
        return "Student{" +
                "stuId='" + stuId + '\'' +
                ", stuName='" + stuName + '\'' +
                ", age=" + age +
                ", photos=" + photos +
                ", photoPath='" + fileNameStr + '\'' +
                ", path=" + path +
                '}';
    }
}
  1. 在 webapp 下新建一个 js 目录,并将 jquery-3.6.0.min.js 添加进该目录下。

  2. 在 webapp/WEB-INF 下新建一个 templates 目录,并在该目录下创建一个 file-upload.html ,代码如下。

html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--引入 jquery-->
    <script type="text/javaScript"
            src="../../js/jquery-3.6.0.min.js" th:src="@{/js/jquery-3.6.0.min.js}"></script>
</head>
<body>
<form th:action="@{/student}" method="post" enctype="multipart/form-data">
    <table style="margin: auto">
        <tr>
            <td th:if="${not #strings.isEmpty(msg)}" colspan="2" align="center">
                <p style="color: red;margin: auto" th:text="${msg}"></p>
            </td>
        </tr>
        <tr>
            <td>学号:</td>
            <td><input type="text" name="stuId" required><br></td>
        </tr>
        <tr>
            <td>学生姓名:</td>
            <td><input type="text" name="stuName" required><br></td>
        </tr>
        <tr>
            <td>年龄:</td>
            <td><input type="number" name="age" required><br></td>
        </tr>
        <tr>
            <td>照片:</td>
            <td><input type="file" id="chooseImage" name="photos" multiple="multiple" required><br>
                <span id="img-div"></span></td>
        </tr>
        <input id="fileNameStr" type="hidden" name="fileNameStr"/>
        <tr>
            <td colspan="2" align="center">
                <input type="submit" value="提交">
                <input type="reset" value="重置">
            </td>
        </tr>
    </table>
    <!-- 保存用户自定义的背景图片 -->
    <img id="preview_photo" src="" width="200px" height="200px">
</form>
<script type="text/javascript" th:inline="javascript">
    /*<![CDATA[*/
    ctxPath = /*[[@{/}]]*/ '';
    /*]]>*/
</script>
<script type="text/javaScript">
    $('#chooseImage').on('change', function () {
        var filePath = $(this).val(),         //获取到input的value,里面是文件的路径
            fileFormat = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
        // 检查是否是图片
        if (!fileFormat.match(/.png|.jpg|.jpeg/)) {
            alert('上传错误,文件格式必须为:png/jpg/jpeg');
            return;
        }
        //获取上传的文件
        var arr = document.getElementById('chooseImage').files;
        //遍历文件
        for (var i = 0; i < arr.length; i++) {
            //通过 FormData 将文件信息提交到后台
            var formData = new FormData();
            formData.append('photo', arr[i]);
            $.ajax({
                url: "http://localhost:8080/springmvc-file-demo/uploadPhoto",
                type: "post",
                data: formData,
                contentType: false,
                processData: false,
                success: function (data) {
                    if (data.type == "success") {
                        //在图片显示区显示图片
                        var html = "<img id='" + data.filename + "' src='" + ctxPath + data.filepath + data.filename + "' width='200px' height='200px'>&nbsp;";
                        $("#img-div").append(html);
                        //将文件路径赋值给 fileNameStr
                        var path = $("#fileNameStr").val();
                        if (path == "") {
                            $("#fileNameStr").val(data.filename);
                        } else {
                            $("#fileNameStr").val(path + "," + data.filename);
                        }
                    } else {
                        alert(data.msg);
                    }
                },
                error: function (data) {
                    alert("上传失败")
                }
            });
        }
    });
</script>
<style>
    img[src=""], img:not([src]) {
        opacity: 0;
    }
</style>
</body>
</html>
  1. 在 net.biancheng.c.controller 包下,创建一个名为 MultiFileController 的控制器类,代码如下。
java
package net.biancheng.c.controller;
import net.biancheng.c.entity.Student;
import org.apache.commons.io.FileUtils;
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.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.*;
@Controller
public class MultiFileController {
    /**
     * 图片上传
     *
     * @param photo
     * @param request
     * @return
     */
    @RequestMapping(value = "/uploadPhoto", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, String> uploadPhoto(MultipartFile photo, HttpServletRequest request) {
        Map<String, String> ret = new HashMap<String, String>();
        if (photo == null) {
            ret.put("type", "error");
            ret.put("msg", "选择要上传的文件!");
            return ret;
        }
        if (photo.getSize() > 1024 * 1024 * 10) {
            ret.put("type", "error");
            ret.put("msg", "文件大小不能超过10M!");
            return ret;
        }
        //获取文件后缀
        String suffix = photo.getOriginalFilename().substring(photo.getOriginalFilename().lastIndexOf(".") + 1, photo.getOriginalFilename().length());
        if (!"jpg,jpeg,gif,png".toUpperCase().contains(suffix.toUpperCase())) {
            ret.put("type", "error");
            ret.put("msg", "请选择jpg、peg、gif、png 格式的图片!");
            return ret;
        }
        String realPath = request.getServletContext().getRealPath("/upload/");
        System.out.println(realPath);
        File fileDir = new File(realPath);
        if (!fileDir.exists()) {
            fileDir.mkdir();
        }
        String filename = photo.getOriginalFilename();
        System.err.println("正在上传的图片为:" + filename);
        String newFileName = UUID.randomUUID() + filename;
        try {
            //将文件保存指定目录
            photo.transferTo(new File(realPath + newFileName));
        } catch (Exception e) {
            ret.put("type", "error");
            ret.put("msg", "保存文件异常!");
            e.printStackTrace();
            return ret;
        }
        ret.put("type", "success");
        ret.put("msg", "上传图片成功!");
        ret.put("filepath", "/upload/");
        ret.put("filename", newFileName);
        return ret;
    }
    /**
     * 提交学生信息
     *
     * @param student
     * @param model
     * @return
     */
    @RequestMapping(value = "/student", method = RequestMethod.POST)
    public String uploadFile(Student student, Model model) {
        String fileNameStr = student.getFileNameStr();
        //将图片路径的字符串拆分,添加到图片路径的集合中
        String[] split = fileNameStr.split(",");
        List<String> list = new ArrayList<>();
        for (String fileName : split) {
            list.add(fileName);
        }
        student.setPath(list);
        model.addAttribute("student", student);
        return "success";
    }
}

5. 在 webapps/WEB-INF/tempaltes 目录下,新建一个 success.html,代码如下。

html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>学生信息上传成功</h1>
<table>
    <tr>
        <td>学号:</td>
        <td th:text="${student.getStuId()}"></td>
    </tr>
    <tr>
        <td>姓名:</td>
        <td th:text="${student.getStuName()}"></td>
    </tr>
    <tr>
        <td>年龄:</td>
        <td th:text="${student.getAge()}"></td>
    </tr>
    <tr>
        <td>照片:</td>
        <td th:each="p:${student.getPath()}">
           <img th:src="${#servletContext.getContextPath()}+'/upload/'+${p}" width='200px' height='200px'/><br>
        </td>
    </tr>
</table>
</body>
</html>
  1. 将 springmvc-file-demo 部署到 Tomcat 服务器中并启动该服务器,使用浏览器访问 http://localhost:8080/springmvc-file-demo/,结果如下图。

图1:Spring MVC 图片上传-1

  1. 在表单中分别填写学号、姓名、年龄等信息,然后点击“选择文件”按钮,如下图。

图2:点击选择文件

  1. 选择需要上传的一张或多张图片,选择完成后,页面中回显了上传的图片,如下图。

图3:图片回显

  1. 点击下方的提交按钮,将学生信息提交到后台,结果如下图。

springmvc 学生信息提交成功
图4:学生信息提交成功