1. 程式人生 > >精盡Spring MVC原始碼分析 - MultipartResolver 元件

精盡Spring MVC原始碼分析 - MultipartResolver 元件

> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版本:5.2.4.RELEASE > > 該系列其他文件請檢視:[**《精盡 Spring MVC 原始碼分析 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14123963.html) `MultipartResolver` 元件,內容型別( `Content-Type` )為 `multipart/*` 的請求的解析器,主要解析檔案上傳的請求。例如,`MultipartResolver` 會將 HttpServletRequest 封裝成 `MultipartHttpServletRequest` 物件,便於獲取引數資訊以及上傳的檔案 使用方式,可以參考[《MyBatis 使用手冊》](https://www.cnblogs.com/lifullmoon/p/14014660.html)中的 **整合 Spring** 模組下的 `spring-mvc.xml` 檔案中配置 `MultipartResolver` 為 `CommonsMultipartResolver` 實現類,然後在方法入參中用 MultipartFile 型別接收 關於在 SpringBoot 中如何使用檔案上傳可參考 [Spring 官方文件](https://spring.io/guides/gs/uploading-files/) ### 回顧 先來回顧一下在 `DispatcherServlet` 中處理請求的過程中哪裡使用到 `MultipartResolver` 元件,可以回到[**《一個請求的旅行過程》**](https://www.cnblogs.com/lifullmoon/p/14131862.html)中的 `DispatcherServlet` 的 `doDispatch` 方法中看看,如下: ```java protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // ... 省略相關程式碼 // <2> 檢測請求是否為上傳請求,如果是則通過 multipartResolver 將其封裝成 MultipartHttpServletRequest 物件 processedRequest = checkMultipart(request); // ... 省略相關程式碼 } protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // 如果該請求是一個涉及到 multipart (檔案)的請求 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } else if (hasMultipartException(request)) { logger.debug("Multipart resolution previously failed for current request - " + "skipping re-resolution for undisturbed error rendering"); } else { try { // 將 HttpServletRequest 請求封裝成 MultipartHttpServletRequest 物件,解析請求裡面的引數以及檔案 return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } // If not returned before: return original request. return request; } ``` `<2>` 處,如果該請求是一個涉及到 multipart (檔案)的請求,則通過 `multipartResolver` 將 `HttpServletRequest` 請求封裝成 `MultipartHttpServletRequest` 物件,解析請求裡面的引數以及檔案 ### MultipartResolver介面 `org.springframework.web.multipart.MultipartResolver` 介面,內容型別( `Content-Type` )為 `multipart/*` 的請求的解析器介面,程式碼如下: ```java public interface MultipartResolver { /** * 是否為 multipart 請求 */ boolean isMultipart(HttpServletRequest request); /** * 將 HttpServletRequest 請求封裝成 MultipartHttpServletRequest 物件 */ MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; /** * 清理處理 multipart 產生的資源,例如臨時檔案 */ void cleanupMultipart(MultipartHttpServletRequest request); } ``` MultipartResolver 介面體系的結構如下:
一共有兩塊: - 上半部分,MultipartRequest 介面及其實現類 - 下半部分,MultipartResolver 介面以及其實現類 ### 初始化過程 在 `DispatcherServlet` 的 `initMultipartResolver(ApplicationContext context)` 方法,初始化 MultipartResolver 元件,方法如下: ```java private void initMultipartResolver(ApplicationContext context) { try { // 從 Spring 上下文中獲取名稱為 "multipartResolver" ,型別為 MultipartResolver 的 Bean this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.multipartResolver); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; if (logger.isTraceEnabled()) { logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared"); } } } ``` - 在 Spring MVC 中,`multipartResolver` 預設為 `null`**【注意】**,需要自己配置,例如[《MyBatis 使用手冊》](https://www.cnblogs.com/lifullmoon/p/14014660.html)中的 **整合 Spring** 模組下的 `spring-mvc.xml` 檔案中配置 MultipartResolver 為 `CommonsMultipartResolver` 實現類,也可以配置為 `StandardServletMultipartResolver` 實現類 - 在 Spring Boot 中,`multipartResolver` 預設為 `StandardServletMultipartResolver` 實現類 目前 Spring 只提供上面兩種實現類,接下來依次進行分析 ### StandardServletMultipartResolver `org.springframework.web.multipart.support.StandardServletMultipartResolver`,實現 MultipartResolver 介面,基於 Servlet 3.0 標準的上傳檔案 API 的 MultipartResolver 實現類,程式碼如下: ```java public class StandardServletMultipartResolver implements MultipartResolver { /** * 是否延遲解析 */ private boolean resolveLazily = false; public void setResolveLazily(boolean resolveLazily) { this.resolveLazily = resolveLazily; } @Override public boolean isMultipart(HttpServletRequest request) { // 請求的 Content-type 必須 multipart/ 開頭 return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/"); } @Override public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); } @Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { // To be on the safe side: explicitly delete the parts, // but only actual file parts (for Resin compatibility) try { // 刪除臨時的 Part for (Part part : request.getParts()) { if (request.getFile(part.getName()) != null) { part.delete(); } } } catch (Throwable ex) { LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex); } } } } ``` - `isMultipart(HttpServletRequest request)`方法,請求的 Content-type 是否以 `multipart/` 開頭 - `resolveMultipart(HttpServletRequest request)`方法,直接將 HttpServletRequest 轉換成 `StandardMultipartHttpServletRequest` 物件 - `cleanupMultipart(MultipartHttpServletRequest request)`方法,清理資源,刪除臨時的 `javax.servlet.http.Part` 們 ### StandardMultipartHttpServletRequest `org.springframework.web.multipart.support.StandardMultipartHttpServletRequest`,繼承 AbstractMultipartHttpServletRequest 抽象類,基於 Servlet 3.0 的 Multipart HttpServletRequest 實現類,包含了一個 `javax.servlet.http.HttpServletRequest` 物件和它的 `javax.servlet.http.Part` 物件們,其中 Part 物件會被封裝成 `StandardMultipartFile` 物件 #### 構造方法 ```java public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { /** * 普通引數名的集合 */ @Nullable private Set multipartParameterNames; public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException { this(request, false); } public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); // 如果不需要延遲解析 if (!lazyParsing) { // 解析請求 parseRequest(request); } } } ``` - `multipartParameterNames`:普通引數名的集合,非上傳檔案的引數名 - 如果不需要延遲解析,則呼叫 `parseRequest(HttpServletRequest request)` 方法,直接解析請求 #### parseRequest `parseRequest(HttpServletRequest request)` 方法,解析請求,解析 `HttpServletRequest` 中的 `Part` 物件,如果是檔案,則封裝成 `StandardMultipartFile` 物件,否則就是普通引數,獲取其名稱,如下: ```java private void parseRequest(HttpServletRequest request) { try { // <1> 從 HttpServletRequest 中獲取 Part 們 Collection parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); MultiValueMap files = new LinkedMultiValueMap<>(parts.size()); // <2> 遍歷 parts 陣列 for (Part part : parts) { // <2.1> 獲得請求頭中的 Content-Disposition 資訊,MIME 協議的擴充套件 String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION); // <2.2> 對 Content-Disposition 資訊進行解析,生成 ContentDisposition 物件 // 包含請求引數資訊,以面向“物件”的形式進行訪問 ContentDisposition disposition = ContentDisposition.parse(headerValue); // <2.3> 獲得檔名 String filename = disposition.getFilename(); // <2.4> 情況一,檔名非空,說明是檔案引數,則建立 StandardMultipartFile 物件 if (filename != null) { if (filename.startsWith("=?") && filename.endsWith("?=")) { filename = MimeDelegate.decode(filename); } files.add(part.getName(), new StandardMultipartFile(part, filename)); } // <2.5> 情況二,檔名為空,說明是普通引數,則儲存引數名稱 else { this.multipartParameterNames.add(part.getName()); } } // <3> 將上面生成的 StandardMultipartFile 檔案物件們,設定到父類的 multipartFiles 屬性中 setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } } ``` 1. 從 HttpServletRequest 中獲取 Part 們 2. 遍歷 `parts` 陣列 1. 從 Part 物件中獲得請求頭中的 `Content-Disposition` 資訊,MIME 協議的擴充套件 2. 對 Content-Disposition 資訊進行解析,生成 `ContentDisposition` 物件,包含請求引數資訊,以面向“物件”的形式進行訪問 3. 從`ContentDisposition` 物件中獲得檔名 4. 情況一,檔名非空,說明是檔案引數,則建立 `StandardMultipartFile` 物件 5. 情況二,檔名為空,說明是普通引數,則儲存引數名稱 3. 將上面生成的 `StandardMultipartFile` 檔案物件們,設定到父類的 `multipartFiles` 屬性中 4. 如果發生異常則丟擲 #### 其他方法 ```java /** 初始化請求 */ @Override protected void initializeMultipart() { parseRequest(getRequest()); } /** 獲取請求中的引數名稱 */ @Override public Enumeration getParameterNames() { if (this.multipartParameterNames == null) { initializeMultipart(); } if (this.multipartParameterNames.isEmpty()) { return super.getParameterNames(); } // Servlet 3.0 getParameterNames() not guaranteed to include multipart form items // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side Set paramNames = new LinkedHashSet<>(); Enumeration paramEnum = super.getParameterNames(); while (paramEnum.hasMoreElements()) { paramNames.add(paramEnum.nextElement()); } paramNames.addAll(this.multipartParameterNames); return Collections.enumeration(paramNames); } /** 獲取請求中的引數,引數名和引數值的對映 */ @Override public Map getParameterMap() { if (this.multipartParameterNames == null) { initializeMultipart(); } if (this.multipartParameterNames.isEmpty()) { return super.getParameterMap(); } // Servlet 3.0 getParameterMap() not guaranteed to include multipart form items // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side Map paramMap = new LinkedHashMap<>(super.getParameterMap()); for (String paramName : this.multipartParameterNames) { if (!paramMap.containsKey(paramName)) { paramMap.put(paramName, getParameterValues(paramName)); } } return paramMap; } /** 獲取請求的 Content-Type 內容型別 */ @Override public String getMultipartContentType(String paramOrFileName) { try { Part part = getPart(paramOrFileName); return (part != null ? part.getContentType() : null); } catch (Throwable ex) { throw new MultipartException("Could not access multipart servlet request", ex); } } /** 獲取請求頭資訊 */ @Override public HttpHeaders getMultipartHeaders(String paramOrFileName) { try { Part part = getPart(paramOrFileName); if (part != null) { HttpHeaders headers = new HttpHeaders(); for (String headerName : part.getHeaderNames()) { headers.put(headerName, new ArrayList<>(part.getHeaders(headerName))); } return headers; } else { return null; } } catch (Throwable ex) { throw new MultipartException("Could not access multipart servlet request", ex); } } ``` ### StandardMultipartFile `org.springframework.web.multipart.support.StandardMultipartHttpServletRequest` 的私有內部靜態類,實現了 `MultipartFile` 介面和 `Serializable` 介面,內部封裝了 `javax.servlet.http.Part` 物件和檔名稱,程式碼如下: ```java private static class StandardMultipartFile implements MultipartFile, Serializable { private final Part part; private final String filename; public StandardMultipartFile(Part part, String filename) { this.part = part; this.filename = filename; } @Override public String getName() { return this.part.getName(); } @Override public String getOriginalFilename() { return this.filename; } @Override public String getContentType() { return this.part.getContentType(); } @Override public boolean isEmpty() { return (this.part.getSize() == 0); } @Override public long getSize() { return this.part.getSize(); } @Override public byte[] getBytes() throws IOException { return FileCopyUtils.copyToByteArray(this.part.getInputStream()); } @Override public InputStream getInputStream() throws IOException { return this.part.getInputStream(); } @Override public void transferTo(File dest) throws IOException, IllegalStateException { this.part.write(dest.getPath()); if (dest.isAbsolute() && !dest.exists()) { // Servlet 3.0 Part.write is not guaranteed to support absolute file paths: // may translate the given path to a relative location within a temp dir // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths). // At least we offloaded the file from memory storage; it'll get deleted // from the temp dir eventually in any case. And for our user's purposes, // we can manually copy it to the requested location as a fallback. FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath())); } } @Override public void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest)); } } ``` 這個類封裝了 Servlet 3.0 的 `Part` 物件,也就是我們常用到的 MultipartFile 物件,支援對檔案的操作,內部其實都是呼叫 `javax.servlet.http.Part` 的方法 ### AbstractMultipartHttpServletRequest `org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest` 抽象類,繼承了 HttpServletRequestWrapper 類,實現了 MultipartHttpServletRequest介面 該類是 `StandardMultipartHttpServletRequest` 和 `DefaultMultipartHttpServletRequest` 的父類,實現了一些公共的方法,程式碼如下: ```java public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest { /** * 請求中的檔案資訊 */ @Nullable private MultiValueMap multipartFiles; protected AbstractMultipartHttpServletRequest(HttpServletRequest request) { super(request); } @Override public HttpServletRequest getRequest() { return (HttpServletRequest) super.getRequest(); } @Override public HttpMethod getRequestMethod() { return HttpMethod.resolve(getRequest().getMethod()); } /** 獲取請求頭資訊 */ @Override public HttpHeaders getRequestHeaders() { HttpHeaders headers = new HttpHeaders(); Enumeration headerNames = getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.put(headerName, Collections.list(getHeaders(headerName))); } return headers; } /** 獲取檔名稱列表 */ @Override public Iterator getFileNames() { return getMultipartFiles().keySet().iterator(); } /** 獲取指定檔名的單個檔案 */ @Override public MultipartFile getFile(String name) { return getMultipartFiles().getFirst(name); } /** 獲取指定檔名的多個檔案 */ @Override public List getFiles(String name) { List multipartFiles = getMultipartFiles().get(name); if (multipartFiles != null) { return multipartFiles; } else { return Collections.emptyList(); } } @Override public Map getFileMap() { return getMultipartFiles().toSingleValueMap(); } @Override public MultiValueMap getMultiFileMap() { return getMultipartFiles(); } public boolean isResolved() { return (this.multipartFiles != null); } protected final void setMultipartFiles(MultiValueMap multipartFiles) { this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles)); } protected MultiValueMap getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } return this.multipartFiles; } /** 交由子類實現 */ protected void initializeMultipart() { throw new IllegalStateException("Multipart request not initialized"); } } ``` 上面的方法都比較簡單,用於獲取請求中的檔案物件 `MultiValueMap multipartFiles`屬性,儲存由子類解析出請求中的 Part 物件所封裝成的 MultipartFile 物件 ### CommonsMultipartResolver `org.springframework.web.multipart.commons.CommonsMultipartResolver`,實現 MultipartResolver、ServletContextAware 介面,繼承 CommonsFileUploadSupport 抽象類,基於 [**Apache Commons FileUpload**](http://commons.apache.org/proper/commons-fileupload) 的 MultipartResolver 實現類 如果需要使用這個 MultipartResolver 實現類,需要引入 `commons-fileupload`、`commons-io` 和 `commons-codec` 元件,例如: ```xml commons-fileupload
commons-fileupload 1.4
commons-io commons-io 2.8.0 commons-codec commons-codec 1.15
``` **注意**,如果 Spring Boot 專案中需要使用 CommonsMultipartResolver,需要在 application.yml 中新增如下配置,排除其預設的配置,如下: ```yml spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration ``` #### 構造方法 ```java public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware { /** * 是否延遲解析 */ private boolean resolveLazily = false; public CommonsMultipartResolver() { super(); } public CommonsMultipartResolver(ServletContext servletContext) { this(); setServletContext(servletContext); } } ``` #### isMultipart ```java @Override public boolean isMultipart(HttpServletRequest request) { // 必須是 POST 請求,且 Content-Type 為 multipart/ 開頭 return ServletFileUpload.isMultipartContent(request); } ``` 判斷是否為 multipart 請求,必須是 `POST` 請求,且 Content-Type 為 `multipart/` 開頭 #### resolveMultipart ```java @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()); } } ``` 將 HttpServletRequest 轉換成 `DefaultMultipartHttpServletRequest` 物件 如果開啟了延遲解析,則重寫該物件的 initializeMultipart() 方法,用於解析請求 否則直接呼叫 `parseRequest(HttpServletRequest request)` 方法解析請求,返回 MultipartParsingResult 物件,包含 MultipartFile 物件和普通引數資訊 #### parseRequest `parseRequest(HttpServletRequest request)`方法,用於解析請求,返回 MultipartParsingResult 物件,包含 MultipartFile 物件、普通引數資訊以及引數的 Content-Type 資訊,方法如下: ```java protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { // <1> 獲取請求中的編碼 String encoding = determineEncoding(request); // <2> 獲取 ServletFileUpload 物件 FileUpload fileUpload = prepareFileUpload(encoding); try { // <3> 獲取請求中的流資料 List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); // <4> 將這些流資料轉換成 MultipartParsingResult,包含 CommonsMultipartFile、引數資訊、Content-type return parseFileItems(fileItems, encoding); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Failed to parse multipart servlet request", ex); } } ``` 1. 獲取請求中的編碼 2. 根據編碼獲取到 ServletFileUpload 物件( `commons-fileupload` 中的類),在 `newFileUpload(FileItemFactory fileItemFactory)` 方法中返回的就是 ServletFileUpload 物件,可以看到父類 CommonsFileUploadSupport 的構造方法,如下: ```java // org.springframework.web.multipart.commons.CommonsFileUploadSupport.java public CommonsFileUploadSupport() { this.fileItemFactory = newFileItemFactory(); // 由子類實現 this.fileUpload = newFileUpload(getFileItemFactory()); } ``` 具體細節就不講述了 3. 通過 ServletFileUpload 物件解析請求,返回流資料 `List fileItems` 4. 呼叫父類 CommonsFileUploadSupport 的 `parseFileItems(List fileItems, String encoding)` 方法,將這些流資料轉換成 MultipartParsingResult 物件 ```java // org.springframework.web.multipart.commons.CommonsFileUploadSupport.java protected MultipartParsingResult parseFileItems(List fileItems, String encoding) { MultiValueMap multipartFiles = new LinkedMultiValueMap<>(); Map multipartParameters = new HashMap<>(); Map multipartParameterContentTypes = new HashMap<>(); // Extract multipart files and multipart parameters. for (FileItem fileItem : fileItems) { if (fileItem.isFormField()) { String value; String partEncoding = determineEncoding(fileItem.getContentType(), encoding); try { value = fileItem.getString(partEncoding); } catch (UnsupportedEncodingException ex) { if (logger.isWarnEnabled()) { logger.warn("Could not decode multipart item '" + fileItem.getFieldName() + "' with encoding '" + partEncoding + "': using platform default"); } value = fileItem.getString(); } String[] curParam = multipartParameters.get(fileItem.getFieldName()); if (curParam == null) { // simple form field multipartParameters.put(fileItem.getFieldName(), new String[] {value}); } else { // array of simple form fields String[] newParam = StringUtils.addStringToArray(curParam, value); multipartParameters.put(fileItem.getFieldName(), newParam); } multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType()); } else { // multipart file field CommonsMultipartFile file = createMultipartFile(fileItem); multipartFiles.add(file.getName(), file); LogFormatUtils.traceDebug(logger, traceOn -> "Part '" + file.getName() + "', size " + file.getSize() + " bytes, filename='" + file.getOriginalFilename() + "'" + (traceOn ? ", storage=" + file.getStorageDescription() : "") ); } } return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes); } ``` 大致就是遍歷 `fileItems` 集合,如果是一個簡單的表單欄位,那麼就是一個普通的引數,將引數名和值儲存起來 否則就是檔案,將其封裝成 `CommonsMultipartFile` 儲存起來 #### cleanupMultipart `cleanupMultipart(MultipartHttpServletRequest request)`方法,清理檔案產生的臨時資源,如下: ```java // CommonsMultipartResolver.java @Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { try { cleanupFileItems(request.getMultiFileMap()); } catch (Throwable ex) { logger.warn("Failed to perform multipart cleanup for servlet request", ex); } } } // CommonsFileUploadSupport.java protected void cleanupFileItems(MultiValueMap multipartFiles) { for (List files : multipartFiles.values()) { for (MultipartFile file : files) { if (file instanceof CommonsMultipartFile) { CommonsMultipartFile cmf = (CommonsMultipartFile) file; cmf.getFileItem().delete(); LogFormatUtils.traceDebug(logger, traceOn -> "Cleaning up part '...")); } } } } ``` ### DefaultMultipartHttpServletRequest `org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest`,繼承 AbstractMultipartHttpServletRequest 抽象類,MultipartHttpServletRequest 的預設實現類,程式碼如下: ```java public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { private static final String CONTENT_TYPE = "Content-Type"; @Nullable private Map multipartParameters; @Nullable private Map multipartParameterContentTypes; public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap mpFiles, Map mpParams, Map mpParamContentTypes) { super(request); setMultipartFiles(mpFiles); setMultipartParameters(mpParams); setMultipartParameterContentTypes(mpParamContentTypes); } public DefaultMultipartHttpServletRequest(HttpServletRequest request) { super(request); } @Override @Nullable public String getParameter(String name) { String[] values = getMultipartParameters().get(name); if (values != null) { return (values.length > 0 ? values[0] : null); } return super.getParameter(name); } @Override public String[] getParameterValues(String name) { String[] parameterValues = super.getParameterValues(name); String[] mpValues = getMultipartParameters().get(name); if (mpValues == null) { return parameterValues; } if (parameterValues == null || getQueryString() == null) { return mpValues; } else { String[] result = new String[mpValues.length + parameterValues.length]; System.arraycopy(mpValues, 0, result, 0, mpValues.length); System.arraycopy(parameterValues, 0, result, mpValues.length, parameterValues.length); return result; } } @Override public Enumeration getParameterNames() { Map multipartParameters = getMultipartParameters(); if (multipartParameters.isEmpty()) { return super.getParameterNames(); } Set paramNames = new LinkedHashSet<>(); paramNames.addAll(Collections.list(super.getParameterNames())); paramNames.addAll(multipartParameters.keySet()); return Collections.enumeration(paramNames); } @Override public Map getParameterMap() { Map result = new LinkedHashMap<>(); Enumeration names = getParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); result.put(name, getParameterValues(name)); } return result; } @Override public String getMultipartContentType(String paramOrFileName) { MultipartFile file = getFile(paramOrFileName); if (file != null) { return file.getContentType(); } else { return getMultipartParameterContentTypes().get(paramOrFileName); } } @Override public HttpHeaders getMultipartHeaders(String paramOrFileName) { String contentType = getMultipartContentType(paramOrFileName); if (contentType != null) { HttpHeaders headers = new HttpHeaders(); headers.add(CONTENT_TYPE, contentType); return headers; } else { return null; } } protected final void setMultipartParameters(Map multipartParameters) { this.multipartParameters = multipartParameters; } protected Map getMultipartParameters() { if (this.multipartParameters == null) { initializeMultipart(); } return this.multipartParameters; } protected final void setMultipartParameterContentTypes(Map multipartParameterContentTypes) { this.multipartParameterContentTypes = multipartParameterContentTypes; } protected Map getMultipartParameterContentTypes() { if (this.multipartParameterContentTypes == null) { initializeMultipart(); } return this.multipartParameterContentTypes; } } ``` 程式碼並不複雜,稍微閱讀一下就理解了