SpringMvc 文件上传开发总结

之前的博客介绍过 Servlet 的文件上传和下载,对于文件下载来说,SpringMvc 跟 Servlet 的实现方式是一样,这里就不再介绍了。对于文件上传来说,虽然 SpringMvc 底层实现方式跟 Servlet 也是一样的,都是使用第三方 commons-fileupload 的 jar 包组件来实现,但是 SpringMvc 隐藏了实现细节,简化了代码编写量,大大提高了开发效率,使用起来非常方便。

本篇博客从实际开发场景出发,通过表单上传和 ajax 异步上传两种代码实现方式进行演示,在博客最后会提供源代码下载。


一、搭建工程

新建一个 maven 项目,导入相关 jar 包,我所导入的 jar 包都是最新的,内容如下:

有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。

<dependencies>
    <!--导入 servlet 相关的 jar 包-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>
    <!--导入 Spring 核心 jar 包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.18</version>
    </dependency>
    <!--导入 SpringMvc 的 jar 包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.18</version>
    </dependency>
    <!--导入 jackson 相关的 jar 包-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.1</version>
    </dependency>
    <!--文件上传组件支持-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
</dependencies>

<build>
    <!--设置插件-->
    <plugins>
        <!--具体的插件配置-->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <port>80</port>
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
</build>

需要注意的是:我们需要导入第三方的 commons-fileupload 文件上传组件,SpringMvc 底层也是使用它来完成文件上传的。

这次我们使用 tomcat 插件来运行网站进行测试,因为这样可以直接在 IDEA 中看到实际运行测试的结果。

有关 tomcat 插件,可以到官网查询,地址为:https://tomcat.apache.org/maven-plugin.html

目前 tomcat 官网的插件,最新版本也就是 tomcat7 的 2.2 版本。

配置好引用的 jar 包后,打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。

搭建好的项目工程整体目录比较简单,具体如下图所示:

image

com.jobs.config 包下存储的是 SpringMvc 的配置文件和 Servlet 的初始化文件
com.jobs.controller 包下存储的是用于提供 api 接口的类
com.jobs.exception包下存储的是全局异常捕获和处理类

web 目录下放置的是网站文件,只有一个静态页面和一些 js 文件


二、SpringMvc 关键配置

有关 ServletInitConfig 和 SpringMvcConfig 的具体内容,跟之前发布的博客相比,重复性内容太多了,这里仅仅介绍关键的配置信息。在 SpringMvcConfig 类中增加 SpringMvc 装载文件上传组件 Bean 的获取方法,具体内容为:

//配置 SpringMvc 上传文件的解析器相关参数,底层依赖于第三方 commons-fileupload 的 jar 包组件
//需要注意的是:这里的上传组件的 Bean 必须取名为 multipartResolver
@Bean("multipartResolver")
public CommonsMultipartResolver getMultipartResolver() {

    CommonsMultipartResolver cmr = new CommonsMultipartResolver();
    cmr.setDefaultEncoding("UTF-8");

    //设置单个文件大小限制(byte),这里设置为 4M
    cmr.setMaxUploadSizePerFile(4 * 1024 * 1024);

    //设置整个请求,最大允许上传的文件总大小限制(byte),这里设置为 10M
    cmr.setMaxUploadSize(10 * 1024 * 1024);

    //设置上传文件过程中,内存最多使用 10M
    cmr.setMaxInMemorySize(10 * 1024 * 1024);

    return cmr;
}

需要注意的是:Bean 的名称必须为 multipartResolver ,否则文件无法上传成功,具体原因可以查看 SpringMvc 的源码。

另外我们还需要有一个全局的异常捕获和处理的类,方便在文件上传的过程中,如果出现问题,不至于显示默认的报错页面。

package com.jobs.exception;

import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Component
@ControllerAdvice
public class ExceptionAdvice {

    //全局捕获所有异常,并进行处理
    @ExceptionHandler(Exception.class)
    public String handException(Exception ex, Model m) {

        //将异常信息,记录到日志中
        System.out.println("捕获到的异常信息:" + ex);

        //给用户展示友好的信息,隐藏具体的问题细节
        m.addAttribute("msg", ex.getMessage());
        return "fail";
    }
}

这里只编写了一个异常处理方法,处理所有的异常。当然这里只是 demo 演示,当捕获到异常时,跳转到 fail.jsp 页面展示错误信息。在实际开发场景中,你可以进行一些人性化的处理和信息提示。错误日志肯定是需要记录的,方便排查和解决问题。


三、上传文件的代码细节

下面展示 Controller 中接收表单上传文件和 ajax 异步提交上传文件的方法,因为上传文件需要进行 Post 提交,因此这两个方法直接加上了 @PostMapping 注解,表示仅仅接收 Post 请求,具体内容如下:

package com.jobs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
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;

@RequestMapping("/test")
@Controller
public class TestController {

    //通过 form 表单上传 2 个文件,分别用参数 file1 和 file2 接收
    @PostMapping("/formUpload")
    public String formUploadFile(HttpServletRequest request,
                                 String remark,
                                 MultipartFile file1,
                                 MultipartFile file2) throws IOException {

        System.out.println("提交过来的文件备注信息为:" + remark);

        /*
        //MultipartFile参数中封装了上传的文件的相关信息

        //获取上传文件的大小(单位:字节)
        System.out.println(file.getSize());
        //获取上传文件的字节数组
        System.out.println(file.getBytes());
        //获取上传文件的 mime 类型
        System.out.println(file.getContentType());
        //这个是参数名称,一般不会使用
        System.out.println(file.getName());
        //获取原始文件名称
        System.out.println(file.getOriginalFilename());
        //判断文件是否为空文件
        System.out.println(file.isEmpty());
        */

        //设置上传后的文件保存的文件夹,如果不存在,就自动创建
        String dir = request.getServletContext().getRealPath("/upload");
        File directory = new File(dir);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        //如果文件不为空的话,就写入磁盘中
        if (file1 != null && !file1.isEmpty()) {

            //这里要求验证单个上传文件,不允许大于 2M
            long size = file1.getSize();
            if (size > 1 * 1024 * 1024) {
                request.setAttribute("msg", "file1上传的文件大于2M");
                return "fail";
            }

            //获取所上传文件的原始文件名
            //你也可以生成一个随机的文件名进行保存
            //这里的 demo 为了演示,就直接使用原始文件名进行写入磁盘了
            String fname = file1.getOriginalFilename();
            //将文件写入到磁盘中
            file1.transferTo(new File(dir, fname));
        }

        if (file2 != null && !file2.isEmpty()) {

            //这里要求验证单个上传文件,不允许大于 2M
            long size = file2.getSize();
            if (size > 1 * 1024 * 1024) {
                request.setAttribute("msg", "file2上传的文件大于2M");
                return "fail";
            }

            String fname = file2.getOriginalFilename();
            file2.transferTo(new File(dir, fname));
        }

        return "success";
    }

    //通过 ajax 表单上传 2 个文件,分别用参数 file1 和 file2 接收
    @PostMapping("/ajaxUpload")
    @ResponseBody
    public String ajaxUploadFile(HttpServletRequest request,
                                 String remark,
                                 MultipartFile file1,
                                 MultipartFile file2) throws IOException {

        System.out.println("提交过来的文件备注信息为:" + remark);

        //设置上传后的文件保存的文件夹,如果不存在,就自动创建
        String dir = request.getServletContext().getRealPath("/upload");
        File directory = new File(dir);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        //如果文件不为空的话,就写入磁盘中
        if (file1 != null && !file1.isEmpty()) {

            //这里要求验证单个上传文件,不允许大于 2M
            long size = file1.getSize();
            if (size > 1 * 1024 * 1024) {
                return "file1上传的文件大于2M";
            }

            //获取所上传文件的原始文件名
            //你也可以生成一个随机的文件名进行保存
            //这里的 demo 为了演示,就直接使用原始文件名进行写入磁盘了
            String fname = file1.getOriginalFilename();
            //将文件写入到磁盘中
            file1.transferTo(new File(dir, fname));
        }

        if (file2 != null && !file2.isEmpty()) {

            //这里要求验证单个上传文件,不允许大于 2M
            long size = file2.getSize();
            if (size > 1 * 1024 * 1024) {
                return "file2上传的文件大于2M";
            }

            String fname = file2.getOriginalFilename();
            file2.transferTo(new File(dir, fname));
        }

        return "ajax上传文件成功";
    }
}

从上面的代码可以看出,SpringMvc 仅仅需要一行代码(使用 MultipartFile 的 transferTo 方法)就实现了文件上传,非常简单。

配合测试文件上传的页面,就是本 Demo 网站的主页 index.html ,具体内容如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>SpringMvc上传文件演示</title>
</head>
<body>
<fieldset>
    <legend>Form 表单上传文件</legend>
    <form action="/test/formUpload" method="post" enctype="multipart/form-data">
        文件信息备注:<input type="text" name="remark"><br/>
        上传文件1:<input type="file" name="file1"><br/>
        上传文件2:<input type="file" name="file2"><br/>
        <input type="submit" value="上传">
    </form>
</fieldset>
<hr/>
<fieldset>
    <legend>Ajax 异步上传文件</legend>
    文件信息备注:<input type="text" id="myRemark"><br/>
    上传文件1:<input type="file" id="myFile1"><br/>
    上传文件2:<input type="file" id="myFile2"><br/>
    <input type="button" value="上传" id="btnUpload">
</fieldset>
<script src="./js/jquery-3.6.0.min.js"></script>
<script src="./js/upload.js"></script>
</body>
</html>

需要注意的是:对于 Form 表单上传文件的方式,其必须要有 enctype="multipart/form-data" 这个属性。

下面列出 ajax 上传文件的 js 内容:

$(function () {
    $('#btnUpload').click(function () {
        //获取 remark 信息
        var remark = $('#myRemark').val();
        //获取第一个文件
        var f1 = $('#myFile1')[0].files[0];
        //获取第二个文件
        var f2 = $('#myFile2')[0].files[0];

        //采用 FormData 组织数据
        var formdata = new FormData();
        formdata.append("remark", remark);
        formdata.append("file1", f1);
        formdata.append("file2", f2);

        $.ajax({
            type: "post",
            url: "/test/ajaxUpload",
            dataType: "json",
            data: formdata,
            cache: false,   //上传文件无需缓存
            processData: false, //使数据不做处理
            contentType: false, //不要设置Content-Type请求头
            success: function (data) {
                alert("返回的数据:" + data);
            }
        });
    });
})

需要注意的是:ajax 上传文件,采用的是 FormData 进行组织数据,在提交的过程中,processData 和 contentType 必须设置为 false 。



到此为止,有关 SpringMvc 通过表单同步提交上传文件和通过 ajax 异步提交上传文件的代码实现方式已经介绍完毕。

本博客 Demo 的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/SpringMvc_FileUpload.zip



posted @ 2022-04-17 21:32  乔京飞  阅读(9558)  评论(0编辑  收藏  举报