该系列文档是本人在学习 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. PHP 使用gd库给图片加图片水印,文字水印,并返回路径

    //图片水印 $mark_filename = './02.jpg'; //获取图片信息 $info = getimagesize($mark_filename); //获取图片的后缀 $type = ...

  2. v-echarts安装

    npm install v-echarts echarts --save

  3. Hadoop window提交出错:Permission denied

    错误信息: Caused by: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.AccessControlExcep ...

  4. ①SpringCloud 实战:引入Eureka组件,完善服务治理

    简介 Netflix Eureka 是一款由 Netflix 开源的基于 REST 服务的注册中心,用于提供服务发现功能.Spring Cloud Eureka 是 Spring Cloud Netf ...

  5. 万字长文!从底层开始带你了解并发编程,彻底帮你搞懂java锁!

    线程是否要锁住同步资源 锁住 悲观锁 不锁住 乐观锁 锁住同步资源失败 线程是否要阻塞 阻塞 不阻塞自旋锁,适应性自旋锁 多个线程竞争同步资源的流程细节有没有区别 不锁住资源,多个线程只有一个能修改资 ...

  6. Postman设置自动捕获传递Cookie教程

    目录 前言 一.安装 1.Postman安装Install Interceptor Bridge 2.谷歌浏览器安装扩展Postman Interceptor 二.使用 1. 打开Capture Co ...

  7. vulnhub: DC 3

    通过nmap扫描,只开放了80端口,并且该web服务是基于Joomla搭建: root@kali:~# nmap -A 192.168.74.140 Starting Nmap 7.80 ( http ...

  8. django绕过admin登录设置

    在admin.py文件添加以下函数本文是转载:#绕过admin登录def allow_anonymous_user(): from django.contrib.auth.models import ...

  9. Leetcode 双周赛#32 题解

    1540 K次操作转变字符串 #计数 题目链接 题意 给定两字符串\(s\)和\(t\),要求你在\(k\)次操作以内将字符串\(s\)转变为\(t\),其中第\(i\)次操作时,可选择如下操作: 选 ...

  10. SpringIOC循环依赖

    目录 1. 什么是循环依赖 注意: 这⾥不是函数的循环调⽤,是对象的相互依赖关系. 循环调⽤其实就是⼀个死循环,除⾮有终结 条件. 2. 循环依赖处理机制 2.1 演示场景: 2.2 处理机制简图 总 ...