一、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屬性來設置臨時位置。