SpringMVC流程分析(三):SpringMVC中处理上传请求的秘密
本系列文章皆在分析SpringMVC
的核心组件和工作原理,让你从SpringMVC
浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC
��工作原理.
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜
前言
本章我们聚焦其中的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