該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.2.4.RELEASEjava
該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》git
MultipartResolver
組件,內容類型( Content-Type
)爲 multipart/*
的請求的解析器,主要解析文件上傳的請求。例如,MultipartResolver
會將 HttpServletRequest 封裝成 MultipartHttpServletRequest
對象,便於獲取參數信息以及上傳的文件github
使用方式,能夠參考《MyBatis 使用手冊》中的 集成 Spring 模塊下的 spring-mvc.xml
文件中配置 MultipartResolver
爲 CommonsMultipartResolver
實現類,而後在方法入參中用 MultipartFile 類型接收web
關於在 SpringBoot 中如何使用文件上傳可參考 Spring 官方文檔spring
先來回顧一下在 DispatcherServlet
中處理請求的過程當中哪裏使用到 MultipartResolver
組件,能夠回到《一個請求的旅行過程》中的 DispatcherServlet
的 doDispatch
方法中看看,以下:apache
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
對象,解析請求裏面的參數以及文件數組
org.springframework.web.multipart.MultipartResolver
接口,內容類型( Content-Type
)爲 multipart/*
的請求的解析器接口,代碼以下:spring-mvc
public interface MultipartResolver { /** * 是否爲 multipart 請求 */ boolean isMultipart(HttpServletRequest request); /** * 將 HttpServletRequest 請求封裝成 MultipartHttpServletRequest 對象 */ MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; /** * 清理處理 multipart 產生的資源,例如臨時文件 */ void cleanupMultipart(MultipartHttpServletRequest request); }
MultipartResolver 接口體系的結構以下:mvc
一共有兩塊:
在 DispatcherServlet
的 initMultipartResolver(ApplicationContext context)
方法,初始化 MultipartResolver 組件,方法以下:
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 使用手冊》中的 集成 Spring 模塊下的 spring-mvc.xml
文件中配置 MultipartResolver 爲 CommonsMultipartResolver
實現類,也能夠配置爲 StandardServletMultipartResolver
實現類
在 Spring Boot 中,multipartResolver
默認爲 StandardServletMultipartResolver
實現類
目前 Spring 只提供上面兩種實現類,接下來依次進行分析
org.springframework.web.multipart.support.StandardServletMultipartResolver
,實現 MultipartResolver 接口,基於 Servlet 3.0 標準的上傳文件 API 的 MultipartResolver 實現類,代碼以下:
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
們
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest
,繼承 AbstractMultipartHttpServletRequest 抽象類,基於 Servlet 3.0 的 Multipart HttpServletRequest 實現類,包含了一個 javax.servlet.http.HttpServletRequest
對象和它的 javax.servlet.http.Part
對象們,其中 Part 對象會被封裝成 StandardMultipartFile
對象
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { /** * 普通參數名的集合 */ @Nullable private Set<String> 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(HttpServletRequest request)
方法,解析請求,解析 HttpServletRequest
中的 Part
對象,若是是文件,則封裝成 StandardMultipartFile
對象,不然就是普通參數,獲取其名稱,以下:
private void parseRequest(HttpServletRequest request) { try { // <1> 從 HttpServletRequest 中獲取 Part 們 Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); MultiValueMap<String, MultipartFile> 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); } }
從 HttpServletRequest 中獲取 Part 們
遍歷 parts
數組
Content-Disposition
信息,MIME 協議的擴展ContentDisposition
對象,包含請求參數信息,以面向「對象」的形式進行訪問ContentDisposition
對象中得到文件名StandardMultipartFile
對象將上面生成的 StandardMultipartFile
文件對象們,設置到父類的 multipartFiles
屬性中
若是發生異常則拋出
/** 初始化請求 */ @Override protected void initializeMultipart() { parseRequest(getRequest()); } /** 獲取請求中的參數名稱 */ @Override public Enumeration<String> 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<String> paramNames = new LinkedHashSet<>(); Enumeration<String> paramEnum = super.getParameterNames(); while (paramEnum.hasMoreElements()) { paramNames.add(paramEnum.nextElement()); } paramNames.addAll(this.multipartParameterNames); return Collections.enumeration(paramNames); } /** 獲取請求中的參數,參數名和參數值的映射 */ @Override public Map<String, String[]> 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<String, String[]> 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); } }
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest
的私有內部靜態類,實現了 MultipartFile
接口和 Serializable
接口,內部封裝了 javax.servlet.http.Part
對象和文件名稱,代碼以下:
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
的方法
org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest
抽象類,繼承了 HttpServletRequestWrapper 類,實現了 MultipartHttpServletRequest接口
該類是 StandardMultipartHttpServletRequest
和 DefaultMultipartHttpServletRequest
的父類,實現了一些公共的方法,代碼以下:
public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest { /** * 請求中的文件信息 */ @Nullable private MultiValueMap<String, MultipartFile> 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<String> headerNames = getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.put(headerName, Collections.list(getHeaders(headerName))); } return headers; } /** 獲取文件名稱列表 */ @Override public Iterator<String> getFileNames() { return getMultipartFiles().keySet().iterator(); } /** 獲取指定文件名的單個文件 */ @Override public MultipartFile getFile(String name) { return getMultipartFiles().getFirst(name); } /** 獲取指定文件名的多個文件 */ @Override public List<MultipartFile> getFiles(String name) { List<MultipartFile> multipartFiles = getMultipartFiles().get(name); if (multipartFiles != null) { return multipartFiles; } else { return Collections.emptyList(); } } @Override public Map<String, MultipartFile> getFileMap() { return getMultipartFiles().toSingleValueMap(); } @Override public MultiValueMap<String, MultipartFile> getMultiFileMap() { return getMultipartFiles(); } public boolean isResolved() { return (this.multipartFiles != null); } protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) { this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles)); } protected MultiValueMap<String, MultipartFile> getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } return this.multipartFiles; } /** 交由子類實現 */ protected void initializeMultipart() { throw new IllegalStateException("Multipart request not initialized"); } }
上面的方法都比較簡單,用於獲取請求中的文件對象
MultiValueMap<String, MultipartFile> multipartFiles
屬性,保存由子類解析出請求中的 Part 對象所封裝成的 MultipartFile 對象
org.springframework.web.multipart.commons.CommonsMultipartResolver
,實現 MultipartResolver、ServletContextAware 接口,繼承 CommonsFileUploadSupport 抽象類,基於 Apache Commons FileUpload 的 MultipartResolver 實現類
若是須要使用這個 MultipartResolver 實現類,須要引入 commons-fileupload
、commons-io
和 commons-codec
組件,例如:
<dependencies> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> </dependencies>
注意,若是 Spring Boot 項目中須要使用 CommonsMultipartResolver,須要在 application.yml 中添加以下配置,排除其默認的配置,以下:
spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware { /** * 是否延遲解析 */ private boolean resolveLazily = false; public CommonsMultipartResolver() { super(); } public CommonsMultipartResolver(ServletContext servletContext) { this(); setServletContext(servletContext); } }
@Override public boolean isMultipart(HttpServletRequest request) { // 必須是 POST 請求,且 Content-Type 爲 multipart/ 開頭 return ServletFileUpload.isMultipartContent(request); }
判斷是否爲 multipart 請求,必須是 POST
請求,且 Content-Type 爲 multipart/
開頭
@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(HttpServletRequest request)
方法,用於解析請求,返回 MultipartParsingResult 對象,包含 MultipartFile 對象、普通參數信息以及參數的 Content-Type 信息,方法以下:
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { // <1> 獲取請求中的編碼 String encoding = determineEncoding(request); // <2> 獲取 ServletFileUpload 對象 FileUpload fileUpload = prepareFileUpload(encoding); try { // <3> 獲取請求中的流數據 List<FileItem> 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); } }
獲取請求中的編碼
根據編碼獲取到 ServletFileUpload 對象( commons-fileupload
中的類),在 newFileUpload(FileItemFactory fileItemFactory)
方法中返回的就是 ServletFileUpload 對象,能夠看到父類 CommonsFileUploadSupport 的構造方法,以下:
// org.springframework.web.multipart.commons.CommonsFileUploadSupport.java public CommonsFileUploadSupport() { this.fileItemFactory = newFileItemFactory(); // 由子類實現 this.fileUpload = newFileUpload(getFileItemFactory()); }
具體細節就不講述了
經過 ServletFileUpload 對象解析請求,返回流數據 List<FileItem> fileItems
調用父類 CommonsFileUploadSupport 的 parseFileItems(List<FileItem> fileItems, String encoding)
方法,將這些流數據轉換成 MultipartParsingResult 對象
// org.springframework.web.multipart.commons.CommonsFileUploadSupport.java protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) { MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>(); Map<String, String[]> multipartParameters = new HashMap<>(); Map<String, String> 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(MultipartHttpServletRequest request)
方法,清理文件產生的臨時資源,以下:
// 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<String, MultipartFile> multipartFiles) { for (List<MultipartFile> 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 '...")); } } } }
org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest
,繼承 AbstractMultipartHttpServletRequest 抽象類,MultipartHttpServletRequest 的默認實現類,代碼以下:
public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { private static final String CONTENT_TYPE = "Content-Type"; @Nullable private Map<String, String[]> multipartParameters; @Nullable private Map<String, String> multipartParameterContentTypes; public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams, Map<String, String> 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<String> getParameterNames() { Map<String, String[]> multipartParameters = getMultipartParameters(); if (multipartParameters.isEmpty()) { return super.getParameterNames(); } Set<String> paramNames = new LinkedHashSet<>(); paramNames.addAll(Collections.list(super.getParameterNames())); paramNames.addAll(multipartParameters.keySet()); return Collections.enumeration(paramNames); } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> result = new LinkedHashMap<>(); Enumeration<String> 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<String, String[]> multipartParameters) { this.multipartParameters = multipartParameters; } protected Map<String, String[]> getMultipartParameters() { if (this.multipartParameters == null) { initializeMultipart(); } return this.multipartParameters; } protected final void setMultipartParameterContentTypes(Map<String, String> multipartParameterContentTypes) { this.multipartParameterContentTypes = multipartParameterContentTypes; } protected Map<String, String> getMultipartParameterContentTypes() { if (this.multipartParameterContentTypes == null) { initializeMultipart(); } return this.multipartParameterContentTypes; } }
代碼並不複雜,稍微閱讀一下就理解了😈
本文對 Spring MVC 處理請求的過程當中使用到的 MultipartResolver 組件進行了分析,若是請求的 Content-Type
爲 multipart/*
,涉及到文件上傳,因此處理請求的第一步須要經過 MultipartResolver 組件對請求進行轉換處理。會將 HttpServletRequest
請求對象封裝成 MultipartHttpServletRequest
對象,便於獲取參數信息和操做上傳的文件(MultipartFile 對象)。
MultipartResolver 組件的實現類有兩種:
org.springframework.web.multipart.support.StandardServletMultipartResolver
:實現 MultipartResolver 接口,基於 Servlet 3.0 標準的上傳文件 API 的 MultipartResolver 實現類org.springframework.web.multipart.commons.CommonsMultipartResolver
:實現 MultipartResolver 接口,基於 Apache Commons FileUpload 的 MultipartResolver 實現類二者的區別:
StandardServletMultipartResolver 會將 HttpServletRequest 封裝成 StandardMultipartHttpServletRequest
對象,由 Servlet 3.0 提供 API 獲取請求中的 javax.servlet.http.Part
對象,而後進行解析,文件會封裝成 StandardMultipartFile
對象
CommonsMultipartResolver 會將 HttpServletRequest 封裝成 DefaultMultipartHttpServletRequest
對象,由 Apache 的 Commons FileUpload 組件來實現,經過 org.apache.commons.fileupload.servlet.ServletFileUpload
對象獲取請求中的 org.apache.commons.fileupload.FileItem
對象,而後進行解析,文件會封裝成 CommonsMultipartFile
對象,如何使用能夠參考上面的 CommonsMultipartResolver 小節
注意事項:
multipartResolver
默認爲 null
,須要本身配置,例如《MyBatis 使用手冊》中的 集成 Spring 模塊下的 spring-mvc.xml
文件中配置 MultipartResolver 爲 CommonsMultipartResolver
實現類,也能夠配置爲 StandardServletMultipartResolver
實現類multipartResolver
默認爲 StandardServletMultipartResolver
實現類參考文章:芋道源碼《精盡 Spring MVC 源碼分析》