Spring MVC文件上传

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

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

1. 编写 form 表单

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

form 表单想要具有文件上传功能,其必须满足以下 3 个条件。
  • form 表单的 method 属性必须设置为 post。
  • form 表单的 enctype 属性设置为 multipart/form-data。
  • 至少提供一个 type 属性为 file 的 input 输入框。

常见的文件上传表单示例代码如下。
<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 个条件外,<input> 标签中还增加了一个 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 的配置文件中对其进行以下配置。
<!--配置文件上传解析器-->
<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 中编写文件上传的方法即可实现文件的上传。
@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 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>

2. 在 src 目录下新建一个 Spring MVC 的配置文件 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">

    <!--开启组件扫描-->
    <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>

3. 在 net.biancheng.c.entity 包下,创建一个名为 Student 的类,代码如下。
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 +
                '}';
    }
}

4. 在 webapp 下新建一个 js 目录,并将 jquery-3.6.0.min.js 添加进该目录下。

5. 在 webapp/WEB-INF 下新建一个 templates 目录,并在该目录下创建一个 file-upload.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>

6. 在 net.biancheng.c.controller 包下,创建一个名为 MultiFileController 的控制器类,代码如下。
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,代码如下。
<!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>

6. 将 springmvc-file-demo 部署到 Tomcat 服务器中并启动该服务器,使用浏览器访问“http://localhost:8080/springmvc-file-demo/”,结果如下图。

图1:Spring MVC 图片上传-1

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

图2:点击选择文件

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

图3:图片回显

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

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