一、Spring boot上传自动配置的有哪些?
1、自动配置MultipartAutoConfiguration类
配置文件在Spring-boot-autoconfig下org.springframework.boot.autoconfigure.web包内
MultipartAutoConfiguration类我们先看下他的关系图
springboot 的源代码如下:
@Configuration @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class }) @ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true) @EnableConfigurationProperties(MultipartProperties.class) public class MultipartAutoConfiguration { private final MultipartProperties multipartProperties; public MultipartAutoConfiguration(MultipartProperties multipartProperties) { this.multipartProperties = multipartProperties; } @Bean @ConditionalOnMissingBean public MultipartConfigElement multipartConfigElement() { return this.multipartProperties.createMultipartConfig(); } @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) @ConditionalOnMissingBean(MultipartResolver.class) public StandardServletMultipartResolver multipartResolver() { StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); return multipartResolver; } }
从这开始我们来逐步分析一下。
我们那可以清楚的知道@ConditionalOnClass这类只是在特定的类加载后才使用。
@ConditionalOnClass({Servlet.class,StandardServletMultipartResolver.class,MultipartConfigElement.class}) @ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true)
使用@EnableConfigurationProperties开启使用自动配置的文件,当配置文件缺失的时候默认 spring.http.multipart.enabled为true
这就是说明默认的时候启用。但是看到这里我们好像还不太清楚我们使用的是什么上传方式,而且上传的具体处理我们好像还不是太清楚。那就继续往下看。
我们可以看下MultipartProperties这个类 ,其实从定义上我们清楚了这是一个资源文件类。
二、上传的通用配置文件说明
代码如下:这里面配置的都是默认情况的配置文件,包括临时目录,文件最大大小,和最大的请求大小等
@ConfigurationProperties(prefix = "spring.http.multipart", ignoreUnknownFields = false) public class MultipartProperties { /** * Enable support of multi-part uploads. * 默认使用multi-part的上传 */ private boolean enabled = true; /** * Intermediate location of uploaded files. */ //上传的临时目录 private String location; /** * Max file size. Values can use the suffixed "MB" or "KB" to indicate a Megabyte or * Kilobyte size. */ private String maxFileSize = "1Mb"; /** * Max request size. Values can use the suffixed "MB" or "KB" to indicate a Megabyte * or Kilobyte size. */ private String maxRequestSize = "10Mb"; /** * Threshold after which files will be written to disk. Values can use the suffixed * "MB" or "KB" to indicate a Megabyte or Kilobyte size. */ private String fileSizeThreshold = "0"; /** * Whether to resolve the multipart request lazily at the time of file or parameter * access. */ private boolean resolveLazily = false; //省略setting和getting方法 /** * Create a new {@link MultipartConfigElement} using the properties. * @return a new {@link MultipartConfigElement} configured using there properties */ public MultipartConfigElement createMultipartConfig() { MultipartConfigFactory factory = new MultipartConfigFactory(); if (StringUtils.hasText(this.fileSizeThreshold)) { factory.setFileSizeThreshold(this.fileSizeThreshold); } if (StringUtils.hasText(this.location)) { factory.setLocation(this.location); } if (StringUtils.hasText(this.maxRequestSize)) { factory.setMaxRequestSize(this.maxRequestSize); } if (StringUtils.hasText(this.maxFileSize)) { factory.setMaxFileSize(this.maxFileSize); } return factory.createMultipartConfig(); }
重点来了:我们看下最后的一个创建 createMultipartConfig方法。这个方法很容易理解就是通过MultipartConfigFactory 来设置上面说的配置项。
那我们在看下MultipartConfigFactory
其实我们看到源码的时候我们视乎感觉和上面的这个类差不多,唯一差别的就是最后一个的createMultipartConfig 返回的 MultipartConfigElement 对象。
这里我们就明白了这就是典型的工厂模式 来加载不用的工具而已。
/** * Factory that can be used to create a {@link MultipartConfigElement}. Size values can be * set using traditional {@literal long} values which are set in bytes or using more * readable {@literal String} variants that accept KB or MB suffixes, for example: * 就是处理传统配置的KB与MB的转换,其他的并没什么。主要是是最后都是kb * <pre class="code"> * factory.setMaxFileSize("10Mb"); * factory.setMaxRequestSize("100Kb"); * </pre> * * @author Phillip Webb * @since 1.4.0 */ public class MultipartConfigFactory { private String location; private long maxFileSize = -1; private long maxRequestSize = -1; private int fileSizeThreshold = 0; //省略 setting和getting /** * Create a new {@link MultipartConfigElement} instance. * @return the multipart config element */ public MultipartConfigElement createMultipartConfig() { return new MultipartConfigElement(this.location, this.maxFileSize, this.maxRequestSize, this.fileSizeThreshold); } }
MultipartConfigElement 就是最终的使用配置文件的类,再看源码我们看到就是有好几个构造方法,可以使用@MultipartConfig注解的方式等等。。。
讲到这里我们基本上清楚了 配置文件有了,这写配置文件也就是我们通用的上传配置。
三、配置文件加载以后,创建处理的组件。
对应自动配置类里的MultipartAutoConfiguration
1 @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)//渲染的组件Bean 2 @ConditionalOnMissingBean(MultipartResolver.class) 3 public StandardServletMultipartResolver multipartResolver() { 4 StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); 5 multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); 6 return multipartResolver; 7 }
四、MultipartResolver介绍。
在springMVC中框架底层实际上也是使用了通用的早期的 jsp smart upload 和 Oreilly 的 COS 类库,以及用最多的 Commons FileUpload 类库,实际开发中,我们只需要使用这些专门针对表单的文件上传处理类库即可。
MultipartResolver 位于 HandlerMapping 之前,请求一来就交由它来处理。当 Web 请求到达 DispatcherServlet 并等待处理的时候,DispatcherServlet 首先会检查能否从自的 WebApplicationContext 中找到一个名称为 multipartResolver(由 DispatcherServlet 的常量 MULTIPART_RESOLVER_BEAN_NAME 所决定)的 MultipartResolver 实例。如果能够获得一个 MultipartResolver 的实例,DispatcherServlet 将调用 MultipartResolver 的 isMultipart(request) 方法检查当前 Web 请求是否为 multipart类型。如果是,DispatcherServlet 将调用 MultipartResolver 的 resolveMultipart(request) 方法,对原始 request 进行装饰,并返回一个 MultipartHttpServletRequest 供后继处理流程使用(最初的 HttpServletRequest 被偷梁换柱成了 MultipartHttpServletRequest),否则,直接返回最初的 HttpServletRequest。
需要特别说明的是MultipartResolver 只是一个接口,具体的实现SpringBoot给我们提供了CommonsMultipartResolver与StandardServletMultipartResolver两种处理方式(这里说的Springboot提供的处理方式,其实就是SpringMVC提供的)。
MultipartResolver 的 isMultipart(request) 方法好实现,当判断出当前的 request 是 multipart 类型的请求,它将调用 MultipartResolve 的 resolveMultipart(request)。这里的 request 就是原始的 HttpServletRequest 对象,奇迹就出现在这里。以 CommonsMultipartResolver 为例,当调用 resolveMultipart(request) 时,看看它是如何创建 MultipartRequest 的。
@Override public boolean isMultipart(HttpServletRequest request) { return (request != null && ServletFileUpload.isMultipartContent(request)); } @Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override protected void initializeMultipart() { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } }
暂且不管 resolveLazily 为何意。假设 resolveLazily 为 false,我们看 else 的片段。由于是 CommonsMultipartResolver,它的 parseRequest 方法将从原始的 HttpServletRequest 中解析出文件,得到基于 Commons FileUpload API 的 FileItem 对象。Spring 在这里封装了一下,对于 MultipartResolver 而言,它看到的就是 MultipartFile。注意最后的 return,它将构建一个 DefaultMultipartHttpServletRequest,也就是 MultipartRequest。它将 MultipartFile 和 MultipartParameter 作为构造函数的参数传入,在这个构造函数里,有 setMultipartFiles 这句话。这个方法正是 AbstractMultipartHttpServletRequest 里的方法,这样,AbstractMultipartHttpServletRequest 的实例变量 multipartFiles 就有正规来源了吧,即解决了上面我们提到的疑问。然去实现 MultipartRequest 接口里的方法就是轻而易举的事了。
五、CommonsMultipartResolver与StandardServletMultipartResolve介绍。
StandardServletMultipartResolve为默认的实现来处理上传,将一个request包装成了一个StandardMultipartHttpServletRequest,这个类会使用parseRequest方法解析http报文,将上传文件封装成StandardMultipartFile挨个存储到MultiValueMap<String, MultipartFile>类型的map中并关联到处理后的request。
StandardServletMultipartResolve是servlet3.0以后的容器才能使用。
CommonsMultipartResolver不会强制要求设置临时文件路径。默认情况下,这个路径就是Servlet容器的临时目录,我们也可以使用uploadTempDir属性来设置临时位置。
知识碎片,重在整理,路很长,一步一个脚印,就好。