该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.2.4.RELEASE

该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》

MultipartResolver 组件,内容类型( Content-Type )为 multipart/* 的请求的解析器,主要解析文件上传的请求。例如,MultipartResolver 会将 HttpServletRequest 封装成 MultipartHttpServletRequest 对象,便于获取参数信息以及上传的文件

使用方式,可以参考《MyBatis 使用手册》中的 集成 Spring 模块下的 spring-mvc.xml 文件中配置 MultipartResolverCommonsMultipartResolver 实现类,然后在方法入参中用 MultipartFile 类型接收

关于在 SpringBoot 中如何使用文件上传可参考 Spring 官方文档

回顾

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 MultipartResolver 组件,可以回到《一个请求的旅行过程》中的 DispatcherServletdoDispatch 方法中看看,如下:

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 (文件)的请求,则通过 multipartResolverHttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件

MultipartResolver接口

org.springframework.web.multipart.MultipartResolver 接口,内容类型( Content-Type )为 multipart/* 的请求的解析器接口,代码如下:

public interface MultipartResolver {
/**
* 是否为 multipart 请求
*/
boolean isMultipart(HttpServletRequest request);
/**
* 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; /**
* 清理处理 multipart 产生的资源,例如临时文件
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}

MultipartResolver 接口体系的结构如下:

一共有两块:

  • 上半部分,MultipartRequest 接口及其实现类
  • 下半部分,MultipartResolver 接口以及其实现类

初始化过程

DispatcherServletinitMultipartResolver(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 只提供上面两种实现类,接下来依次进行分析

StandardServletMultipartResolver

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

StandardMultipartHttpServletRequest

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

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);
}
}
  1. 从 HttpServletRequest 中获取 Part 们

  2. 遍历 parts 数组

    1. 从 Part 对象中获得请求头中的 Content-Disposition 信息,MIME 协议的扩展
    2. 对 Content-Disposition 信息进行解析,生成 ContentDisposition 对象,包含请求参数信息,以面向“对象”的形式进行访问
    3. ContentDisposition 对象中获得文件名
    4. 情况一,文件名非空,说明是文件参数,则创建 StandardMultipartFile 对象
    5. 情况二,文件名为空,说明是普通参数,则保存参数名称
  3. 将上面生成的 StandardMultipartFile 文件对象们,设置到父类的 multipartFiles 属性中

  4. 如果发生异常则抛出

其他方法

/** 初始化请求 */
@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);
}
}

StandardMultipartFile

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 的方法

AbstractMultipartHttpServletRequest

org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest 抽象类,继承了 HttpServletRequestWrapper 类,实现了 MultipartHttpServletRequest接口

该类是 StandardMultipartHttpServletRequestDefaultMultipartHttpServletRequest 的父类,实现了一些公共的方法,代码如下:

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 对象

CommonsMultipartResolver

org.springframework.web.multipart.commons.CommonsMultipartResolver,实现 MultipartResolver、ServletContextAware 接口,继承 CommonsFileUploadSupport 抽象类,基于 Apache Commons FileUpload 的 MultipartResolver 实现类

如果需要使用这个 MultipartResolver 实现类,需要引入 commons-fileuploadcommons-iocommons-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);
}
}

isMultipart

@Override
public boolean isMultipart(HttpServletRequest request) {
// 必须是 POST 请求,且 Content-Type 为 multipart/ 开头
return ServletFileUpload.isMultipartContent(request);
}

判断是否为 multipart 请求,必须是 POST 请求,且 Content-Type 为 multipart/ 开头

resolveMultipart

@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 信息,方法如下:

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);
}
}
  1. 获取请求中的编码

  2. 根据编码获取到 ServletFileUpload 对象( commons-fileupload 中的类),在 newFileUpload(FileItemFactory fileItemFactory) 方法中返回的就是 ServletFileUpload 对象,可以看到父类 CommonsFileUploadSupport 的构造方法,如下:

    // org.springframework.web.multipart.commons.CommonsFileUploadSupport.java
    
    public CommonsFileUploadSupport() {
    this.fileItemFactory = newFileItemFactory();
    // 由子类实现
    this.fileUpload = newFileUpload(getFileItemFactory());
    }

    具体细节就不讲述了

  3. 通过 ServletFileUpload 对象解析请求,返回流数据 List<FileItem> fileItems

  4. 调用父类 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

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 '..."));
}
}
}
}

DefaultMultipartHttpServletRequest

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 组件的更多相关文章

  1. 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  2. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. Web基础_0x00_Web工作方式

    web工作方式 对于普通的上网过程,系统其实是这样做的:浏览器本身是一个客户端,当输入URL的时候,首先浏览器会去请求DNS服务器,通过NDS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服 ...

  2. 超强工具集——GitHub 热点速览 Vol.47

    作者:HelloGitHub-小鱼干 本周 GitHub 趋势榜精彩至极,先是 JetBrains 开源的图像渲染引擎 Skia 有了 Java 封装,一开源便获得 500+ star,再是支持开发者 ...

  3. C#设计模式-外观模式(Facade Pattern)

    引言 在软件测试中,一般都是在功能测试稳定的情况下再进行UI自动化测试.或者进行性能测试.如果一个一个进行太麻烦,此时可以使用对外提供一个简单接口,通过这个接口可以访问内部一群接口.例如进行UI自动化 ...

  4. Android呼吸灯添加

    平台:mtk 一.hal层入口    Lights.c (vendor\mediatek\proprietary\hardware\liblights)     char const*const RE ...

  5. 如何使用ABBYY FineReader 手动管理文档区域

    在运用OCR编辑器时,ABBYY FineReader 15(Windows系统)OCR文字识别软件会对扫描仪或数码相机导入的图像进行识别和检测.在识别和检测之前,软件会自动对PDF文档中的文本.图片 ...

  6. 网络系列之GET与POST请求方式的区别

    作为一枚正在学习前端的 小萌新,如果下面哪里有写的不对的话,可以帮我指出来吗,谢谢 1.是基于什么前提的?如果什么前提都没有,不使用任何规范,只考虑语法和理论上的HTTP协议 那么GET和POST几乎 ...

  7. 自定义JSTL Tag

    <?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java ...

  8. java中String类的使用

    一.Strng类的概念 String类在我们开发中经常使用,在jdk1.8版本之前(包括1.8),String类的底层是一个char类型的数组,1.8版本之后是byte类型的数组,正是因为String ...

  9. Jmeter测试TCP协议

    最近做了个项目是TCP协议传输数据,于是马上想试试JMeter测试TCP. 1首先需要下载第三方的TCP插件包ApacheJMeter_tcp.jar,下载步骤请自行百度. 2.下载完重新配置jmet ...

  10. 4 种高可用 RocketMQ 集群搭建方案!

    背景 笔者所在的业务线,最初化分为三个服务,由于业务初期业务复杂度相对简单,三个业务服务都能很好的独立完成业务功能. 随着产品迭代,业务功能越来越多后慢慢也要面对高并发.业务解耦.分布式事务等问题,所 ...