SpringBoot深度探究(二)官网解读Multipart Resolver完成上传功能

本文介绍了SpringBoot中的文件上传功能,包括MultipartResolver的作用及其实现方式。探讨了ApacheCommonsFileUpload与Servlet3.0技术在SpringMVC中的应用。

前言

上一篇【SpringBoot深度探究(一)】模拟了一个SpringBoot启动Tomcat的过程,其核心技术就是Servlet3.0提供的SPI做的服务增强。这篇将会继续探究SpringBoot的其他核心功能。比如这篇博客要说的Multipart Resolver,主要用来做上传功能的模块,我们将会从解读Spring官网的使用样例说到为什么Spring要这样做。更多Spring内容进入【Spring解读系列目录】

Spring MVC官网推荐的上传技术

官网推荐的上传技术简单来说就是MultipartResolver,对此【SpringMVC官网】上做了下面的说明,一共推荐了两种技术。

MultipartResolver from the org.springframework.web.multipart package is a strategy for parsing multipart requests including file uploads. There is one implementation based on Commons FileUpload and another based on Servlet 3.0 multipart request parsing.

MultipartResolver来自于org.springframework.web.multipart包,是一种解析复合请求的一种策略,包括文件上传。并且推荐了两种技术了两种上传技术:一种是基于实现Commons FileUpload,另外一种是基于Servlet3.0的符合请求解析。可见Servlet3.0做了相当大的更新,SPI的技术也是出自Servlet3.0版本。

To enable multipart handling, you need to declare a MultipartResolver bean in your DispatcherServlet Spring configuration with a name of multipartResolver. The DispatcherServlet detects it and applies it to the incoming request. When a POST with content-type of multipart/form-data is received, the resolver parses the content and wraps the current HttpServletRequest as MultipartHttpServletRequest to provide access to resolved parts in addition to exposing them as request parameters.

为了能够处理复合请求,用户需要在DispatcherServlet里面声明一个MultipartResolver的bean,并且在Spring配置中命名为multipartResolver(注:这个名字是固定不变的)。做好以后DispatcherServlet就可以探测到它并且应用它到进入的请求中。当收到一个POST请求的内容类型(content-type)是multipart/form-data的时候,解析器就会解析内容并把HttpServletRequest请求包装成为一个MultipartHttpServletRequest请求,以便提供对解析部分的访问,此外还会把他们作为请求参数暴露出去。

Apache Commons FileUpload

既然Spring官网提供了两种那么我们一个一个的看。先看Apache Commons FileUpload。

To use Apache Commons FileUpload, you can configure a bean of type CommonsMultipartResolver with a name of multipartResolver. You also need to have commons-fileupload as a dependency on your classpath.

官网大意是说要使用Apache Commons FileUpload,必须先配置一个叫做CommonsMultipartResolver的bean,并且这个这个bean要以multipartResolver命名。然后也需要一个commons-fileupload的依赖。

Commons FileUpload Sample

官网说的一笔带过,具体要怎么做呢,写个Sample具体说明一下。注:演示MultipartResolver的用法,会直接使用html做Sample。如果用JSP需要额外配置视图解析器,参考【SpringMVC新版本(Spring 5.3.0)官网详解(一)】AppConfig的配置。
引入必要的依赖:

<!--Spring 上下文包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<!--Spring web依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<!--servlet依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<!--apache 文件上传依赖-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

在配置类AppConfig中创建CommonsMultipartResolver的bean。

@ComponentScan("com.demo")
@Configuration
public class AppConfig implements WebMvcConfigurer {
    //按照官网构建一个bean,命名为multipartResolver
    @Bean("multipartResolver")
    public CommonsMultipartResolver commonsMultipartResolver(){
        CommonsMultipartResolver commonsMultipartResolv=new CommonsMultipartResolver();
        return commonsMultipartResolv;
    }
}

然后构建DispatcherServlet

public class MybootInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext anno = new AnnotationConfigWebApplicationContext();
        anno.register(AppConfig.class);
        anno.refresh();
        DispatcherServlet servlet = new DispatcherServlet(anno);
        ServletRegistration.Dynamic registration = servletContext.addServlet("myBootServlet", servlet);
        registration.setLoadOnStartup(1);
        //为了方便演示,我们只拦截.action的请求,对于其他资源一律放过
        registration.addMapping("*.action");
    }
}

创建Controller用于接收请求,处理逻辑,只要验证到file对象传递进来了,就算是成功了。

@Controller
public class uploadController {
    @RequestMapping("/upload")
    public void upload(@RequestPart("fileName") MultipartFile multipartFile){
        //输出文件名
        System.out.println(multipartFile.getOriginalFilename());
        //输出文件大小
        System.out.println(multipartFile.getSize());
        /*上面已经拿到传递进来的文件内容,下面就是Java IO操作了,这里就可以自由发挥了*/
    }
}

创建index.html作为测试页面,默认的访问路径是http://localhost:8080/项目名/index.html

<html>
<body>
    <form method="post" action="upload.action" enctype ="multipart/form-data">
        <input type="file" name="fileName">
        <input type="submit">
    </form>
</body>
</html>

随便选择一个文件,上传提交,打印文件信息完成功能测试。

Nov 11, 2020 10:43:35 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
new 1.txt	//文件名
622		//文件大小

组织结构如下。
在这里插入图片描述

Servlet 3.0 MultipartResolver

Servlet 3.0的技术在官网上也同样有介绍,如果使用Servlet 3.0的技术去实现MultipartResolver,就需要在Java里注册Servlet的时候设置一个MultipartConfigElement。同样给了xml的配置方法。直接基于Servlet的功能就不要commons-fileupload依赖了。
在这里插入图片描述

笔者这里没有选择摘录而是直接贴出图片,是因为上面图片里的内容在【SpringBoot的官方文档】上有一个更有意思的表达,SpringBoot官网上是这么说的。

Spring Boot embraces the Servlet 3 javax.servlet.http.Part API to support uploading files. By default, Spring Boot configures Spring MVC with a maximum size of 1MB per file and a maximum of 10MB of file data in a single request. You may override these values, the location to which intermediate data is stored (for example, to the /tmp directory), and the threshold past which data is flushed to disk by using the properties exposed in the MultipartProperties class. For example, if you want to specify that files be unlimited, set the spring.servlet.multipart.max-file-size property to -1.

SpringBoot嵌入了Servlet 3 javax.servlet.http.Part API去支持文件上传。默认情况下SpringBoot配置了Spring MVC中支持一个单一请求的文件大小是最大1MB每个file或者最大10MB的文件数据。用户也可以用MultipartProperties 里面的属性去重写这些值,重写存储数据的位置,或者刷新磁盘。比如说想要上传的文件是不受大小限制的,可以设置spring.servlet.multipart.max-file-size 这个值为-1即可。

The multipart support is helpful when you want to receive multipart encoded file data as a @RequestParam-annotated parameter of type MultipartFile in a Spring MVC controller handler method.

这种复合支持非常有用,尤其是当你想要在一个SpringMVC控制器的处理方法中收到一个类型为MultipartFile 的复合编码文件作为@RequestParam注解参数的时候。

罗嗦了一大堆其就是就是在说SpringBoot已经集成了Servlet 3里的上传功能了,直接在Conrtoller的方法中作为参数就可以使用。但是SpringBoot官网还给了一个小tip。
在这里插入图片描述

这是什么意思呢?SpringBoot官网推荐使用容器内置支持去做复合上传功能,而不是引入一个外部依赖,比如引入Apache Commons FileUpload做上传。是不是感觉SpringBoot简直就是个心机bitch,在外面(Spring MVC官网)说的好好的大家一起玩,反手就指名道姓的给了一巴掌。既然SpringBoot已经推荐大家使用Servlet 3内置功能,就是说笔者上面的章节其实就是写了个寂寞,大家可以当作知识扩展去看。

Servlet 3.0 MultipartResolver Sample

既然SpringBoot默认是Servlet3.0去实现的上传功能,那么就得去看看SpringBoot中Servlet3.0 的MultipartResolver是怎么操作的。根据Spring MVC官网上的说法,我们只需要做到StandardServletMultipartResolver作为一个bean注册到Spring容器,然后再把MultipartConfigElement注册到Servlet里面就可以了。那么就把这个类注册到DispatcherServlet里面,这里注意bean的名字依然要叫做multipartResolver。原因博客最后会分析。
首先修改AppConfig配置类更改bean的实现为StandardServletMultipartResolver

@ComponentScan("com.demo")
@Configuration
public class AppConfig implements WebMvcConfigurer  {
    //按照官网构建一个bean,命名为multipartResolver
    @Bean("multipartResolver")
    public StandardServletMultipartResolver standardServletMultipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        return multipartResolver;
    }
}

其次把MultipartConfigElement注册到DispatcherServlet里面。

public class MybootInitializer  implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext anno = new AnnotationConfigWebApplicationContext();
        anno.register(AppConfig.class);
        anno.refresh();
        DispatcherServlet servlet = new DispatcherServlet(anno);
        ServletRegistration.Dynamic registration = servletContext.addServlet("myBootServlet", servlet);
        registration.setLoadOnStartup(1);
        long maxFileSize = 1024*1024;
        long maxRequestSize = 1024*1024*10;
        int fileSizeThreshold = 0;
        //设置MultipartConfigElement,上面三个值是可选项,只要路径能访问就可以了
        MultipartConfigElement multipartConfigElement = new MultipartConfigElement("D:\\com",maxFileSize,maxRequestSize,fileSizeThreshold);
        //注册到DispatcherServlet里面
        registration.setMultipartConfig(multipartConfigElement);
        //为了方便演示,我们只拦截.action的请求,对于其他资源一律放过
        registration.addMapping("*.action");
    }
}

其他的都不需要改变,直接运行,上传文件成功。

Nov 11, 2020 3:12:28 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
new 2.java
1553

总结

本篇博客简单说了下Spring Boot基于Spring MVC框架下上传文件的功能。其实如果使用SpringBoot用户是感觉不到使用的是什么技术的。因为SpringBoot已经默认上传文件的功能是基于Servlet3.0做的,所有的构建bean的过程,以及注册到DispatcherServlet过程对用户来说都是透明的。因此用户如果使用SpringBoot可以直接在Controller的方法里面写参数的形式为@RequestPart("fileName") MultipartFile multipartFile即可接收参数。至于Spring公司为什么在SpringBoot上不推荐使用Apache的文件上传组件,笔者认为是有道理的,因为二者上传技术相似,就没有必要再多引入一个外部依赖进入项目,从而减少项目的依赖包。

附:多一点分析

不知道各位在笔者贴出来、或者浏览Spring官网相关内容的时候是否有发现这样一个问题:在上传文件的功能上Spring特别强调了bean的名字必须是multipartResolver。之所以SpringBoot会写死是因为Spring希望用户可以根据自己的选择进行上传功能的实现。无论是使用StandardServletMultipartResolver,还是使用CommonsMultipartResolver,都只需要修改一个Bean就可以完成切换,因为二者同时实现了MultipartResolver接口。如果在项目重不希望使用StandardServletMultipartResolver,那么直接用CommonsMultipartResolver的bean把原来的覆盖掉就可以了。上传组件只能互相覆盖的话,就可以保证Spring中永远只有一种上传技术可以使用,避免了Sping选择的混乱,这也就是为什么Spring要写死bean的名字的原因。

CommonsMultipartResolver
在这里插入图片描述
StandardServletMultipartResolver
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值