精盡Spring MVC原始碼分析 - MultipartResolver 元件
阿新 • • 發佈:2020-12-15
> 該系列文件是本人在學習 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;
}
}
```
程式碼並不複雜,稍微閱讀一下就理解了