SpringMVC流程分析(三):MultipartResolver组件——SpringMVC中处理上传请求的关键
本系列文章皆在分析SpringMVC的核心组件和工作原理,让你从springmvc浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC的工作原理SpringMVC.
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜
前言
在上一章SpringMVC流程分析(二):揭开DispatcherServlet的神秘面纱 中,我们分析清楚了 DispatcherServlet对于Http请求的处理主要委托于内部的doDispatch方法进行完成,而doDispatch调用链如下所示:

本章我们聚焦其中的chekMultipart方法,并以此为基础分析SpringMVC中的用于处理上传请求的MultipartResolver组件。

checkMulipart的内部处理逻辑
在开始分析MultipartResolver组件之前,我们先进入到checkMultipart方法内部,从而了解其内部的处理过程。checkMultipart代码如下:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 判断是否为一个multipart 请求
if (this.multipartResolver != null
&& this.multipartResolver.isMultipart(request)) {
// .....省略其他无关代码
// 将请求封装成 MultipartHttpServletRequest类型,并将请求中参数解析为文件
return this.multipartResolver.resolveMultipart(request);
}
return request;
}
- 首先,该方法会调用
isMultipart检查传入的HttpServletRequest请求对象中是否为文件上传的请求。 - 随后,该方法会调用
resolveMultipart方法,来将请求中的上传信息解析为文件对象的形式进行保存,从而方便后续处理。
上述checkMultipart方法内部所调用的isMultipart和resolveMultipart正是组件MultipartResolver内部所定义的方法。接下来,我们将深入分析MultipartResolver的相关内容。
概览MultipartResolver组件
MultipartResolver 是SpringMVC中的一个重要的组件。具体来看,MultipartResolver 的作用主要是对文件上传的请求进行解析,并将文件数据提取出来,从而方便后续直接获取上传数据,而无需再手动的对上传数据进行解析和处理。其中,MultipartResolver接口定义如下所示:
public interface MultipartResolver {
/**
* 当为上传文件时其类型为 multipart/form-data,
*/
boolean isMultipart(HttpServletRequest request);
/**
* 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/**
* 清理处理 multipart 产生的资源,例如临时文件
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
上述方法的具体作用如下:
boolean isMultipart(HttpServletRequest request): 这个方法用来判断当前请求是否为multipart请求,即判断请求的Content-Type是否为multipart/form-data。MultipartHttpServletRequest resolveMultipart(HttpServletRequest request): 这个方法用于将multipart请求解析为MultipartHttpServletRequest对象。MultipartHttpServletRequest是Spring提供的一个扩展HttpServletRequest接口的类,它可以让你方便地获取文件上传的数据。void cleanupMultipart(MultipartHttpServletRequest request): 在文件上传处理完成后,需要调用这个方法来清理临时的文件和资源。
MultipartResolver的体系结构

上图展示了两部分内容:
-
上半部分展示了
MultipartRequest接口及其实现类,MultipartRequest中实现的方式不同,则resolveMultipart方法的解析出的MultipartRequest会有所差异。但不管怎么变化,其本质都是MultipartRequest类型。 -
下半部分展示了
MultipartResolver接口以及其实现类,SpringMVC中对于MultipartResolver的组件的默认实现有两种,分别为StadardMultipartHttpServletRequst和CommonMultipartResolver。
(注:resolveMultipart的处理逻辑相当于替换了上传请求信息,从而将其包装成为一个MultipartHttperServletRequest)
不同MultipartResolver实现类间的区别
通过上图我们注意到,MultipartResolver 默认有两个具体的实现分别为CommonsMultipartResolver和StandardServletMultipartResolver。接下来,我们深入到的其内部源码,来深入分析两者间的区别。其中StandardServletMultipartResolver的内容如下:
public class StandardServletMultipartResolver
implements MultipartResolver {
@Override
public boolean isMultipart(HttpServletRequest request) {
// 请求的 Content-type 必须 multipart/ 开头
return StringUtils.startsWithIgnoreCase(request.getContentType(),
"multipart/");
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
// 将请求包装为一个StandardMultipartHttpServletRequest类型
return new StandardMultipartHttpServletRequest(request,
this.resolveLazily);
}
在StandardServletMultipartResolver中,isMultipart方法在判断请求时通过判断Conten-type中是否包含multipart/字段来判断请求是否为一个上传请求; 而resolveMultipart对于请求的处理,其主要是对HttpServletRequest进行分封装并返回一个StandardMultipartHttpServletRequest的对象,从而方便后续直接从Request中获取上传数据流中的文件信息。
事实上,CommonsMultipartResolver 中的逻辑与其大致相似,所以我们便不再重复论述。下表展示了CommonsMultipartResolver和StandardServletMultipartResolver间的具体差异。
| 组件 | StandardServletMultipartResolver | CommonsMultipartResolver |
|---|---|---|
| 实现接口 | MultipartResovler | MultipartResovler |
| 依赖组件 | 无 | Commons FileUpload |
| 请求封装 | StandardMultipartHttpServletRequest | DefaultMultipartHttpServletRequest |
为了方便后续内容理解,在此我们将对前文叙述内容进行一次总结梳理,以梳理清楚本文的叙述脉络。
首先,我们的主要目的在于探究Http 请求在 DispatcherServlet 中的处理过程。 对此重点关注了其内部的 doDispatch 方法,由于checkMultipart是doDispatch方法中最先执行的方法。所以我们以此为突破口,分析了checkMultipart的内部逻辑。研究表明,该方法会通过 MultipartResolver 组件提供的isMultipart和resolveMultipart方法来判断是否为上传请求,并进行请求封装。随后,我们将关注的重点落点到 MultipartResolver 组件,分析了组件的功能、类层次结构以及其实现类 CommonsMultipartResolver 和 StandardServletMultipartResolver 的差异。结果表明,CommonsMultipartResolver和StandardServletMultipartResolver间的差异主要在于方法resolveMultipart对于请求的封装会返回不同的MultipartRequest。
所以,接下来我们会将关注的重点放在MultipartRequest的分析上。
概览MultipartRequest
我们注意到,CommonsMultipartResolver和StandardServletMultipartResolver之间之间最大的差异来源于resolveMultipart方法中对于HttpServletRequst的封装有所区别。因此,接下来我们将深入分析StandardMultipartHttpServletRequest与DefaultMultipartHttpServletRequest 的差异。
MultipartRequest接口
在 Spring MVC 中,MultipartRequest 是一个接口,它继承了 javax.servlet.http.HttpServletRequest,用于处理文件上传的请求。当客户端通过表单提交包含文件上传的请求(multipart/form-data)时,MultipartResolver 会将原始的 HttpServletRequest 请求对象进行封装,并生成一个实现了 MultipartRequest 接口的对象。而StandardMultipartHttpServletRequest与DefaultMultipartHttpServletRequest是SpringMVC中常用到的两个类 。
StandardMultipartHttpServletRequest的秘密
在 Spring MVC 中,StandardMultipartHttpServletRequest 是 MultipartRequest 接口的一个实现类在 Servlet 3.0+ 环境下,当客户端通过表单提交包含文件上传的请求时,StandardServletMultipartResolver 将原始的 HttpServletRequest 请求对象进行解析,并生成一个 StandardMultipartHttpServletRequest 实例,该实例是对文件上传请求的封装。相关代码如下:
public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
// ... 省略其他无关代码
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
super(request);
// 如果不需要延迟解析
if (!lazyParsing) {
// 解析请求
parseRequest(request);
}
}
// ... 省略其他无关代码
}
不难发现,在StandardMultipartHttpServletRequest的构造其中,会直接调用parseRequest方法,来完成对于请求的处理。相关代码如下所示:
StandardMultipartHttpServletRequest#parseRequest()
private void parseRequest(HttpServletRequest request) {
try {
// <1> 从 HttpServletRequest 中获取 Part 们
// `Part` 接口提供了标准的方法来获取上传文件的信息和内容
Collection<Part> parts = request.getParts();
// <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数组- 从
Part对象中获得请求头中的Content-Disposition信息,MIME 协议的扩展 - 对
Content-Disposition信息进行解析,生成ContentDisposition对象 - 从
ContentDisposition对象中获得文件名 - 情况一,文件名非空,说明是文件参数,则创建
StandardMultipartFile对象 - 情况二,文件名为空,说明是普通参数,则保存参数名称
- 从
-
将上面生成的
StandardMultipartFile文件对象们,设置到父类的multipartFiles属性中,方便后续调用
注:Part 接口提供了标准的方法来获取上传文件的信息和内容,同时上述解析完成文件的对象类型为StandardMultipartFile
DefaultMultipartHttpServletRequest的秘密
DefaultMultipartHttpServletRequest 是 SpringMVC中另一个用于处理文件上传的请求的类。其同样实现了它实现了MultipartRequest 接口,区别于StandardMultipartHttpServletRequest的主要地方在于其使用时基于 Apache Commons FileUpload 库的组件信息。
同StandardMultipartHttpServletRequest一样,我们重点关注其内部方法parseRequest()的实现:
DefaultMultipartHttpServletRequest#parseRequest()
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);
}
// ....省略异常捕获相关代码
}
可以看到其处理逻辑与StandardMultipartHttpServletRequest#parseRequest()中的逻辑大致相似,所以便不再重复叙述。
总的来看, DefaultMultipartHttpServletRequest和 StandardMultipartHttpServletRequest 都是 SpringMVC 中用于处理多部分请求的实现类,用于处理文件上传和访问上传的文件。它们的具体实现方式有所不同,但提供了类似的方法来获取上传的文件和其他表单字段的值。 DefaultMultipartHttpServletRequest 是 SpringMVC 自己的实现,而 StandardMultipartHttpServletRequest 则是基于Servlet 3.0+ 规范的实现。
总结
本文以DispatcherServlet中的doDispatch方法为切入点,重点分析了其中checkMultipart的方法,讨论了SpringMVC 在处理请求的过程中使用到的 MultipartResolver 组件。在此基础上,研究了MultipartResolver组件对于请求的转换处理。具体来看,其可将 HttpServletRequest 请求对象封装成 MultipartHttpServletRequest 对象,从而方便从请求中获取获取参数信息和并将上传数据信息转为 MultipartFile 对象,以方便后续操作。
除此之外,如果我们自定义一个Http请求解析也是可以,其关键就在于我们需要修改isMultipart的判定逻辑,以及resolveMultipart的处理逻辑。同时,还应该保证经过resolveMultipart方法处理后,可以返回一个ServletRequest的对象。
注意事项:
-
在
Spring MVC中,multipartResolver默认为null,需要自己配置,此时可配置MultipartResolver为CommonsMultipartResolver实现类,也可以配置为StandardServletMultipartResolver实现类 -
在 Spring Boot 中,
multipartResolver默认为StandardServletMultipartResolver实现类,如果要更换则需要将MultipartResolver的自动装配进行关闭,可通过@SpringBootApplication(exclude = MultipartAutoConfiguration.class)或配置文件形式进行实现。
转载自:https://juejin.cn/post/7258483153869176892