SpringMVC源码(1)-文件上传请求
我们文件上传接口只需要在方法参数上写
MultipartFile
类,mvc就可以帮我们把上传的文件封装为这个类的对
象供我们非常方便的操作,那它是怎么做的呢?我们一起来看看
我们发的请求默认都是由
DispatcherServlet
类的
doDispatch()
来处理,这个方法的逻辑处理的第一步就是处理文件上传的请求,我们一起来看看是怎么处理的吧。
本文分析的问题
:文件上传请求的执行原理、文件上传自动配置原理
执行流程原理
checkMultipart()
processedRequest = checkMultipart(request)
:处理文件上传请求。所以我们把这个方法看明白就知道了
@Nullable
// 文件上传解析器,只能有一个
private MultipartResolver multipartResolver;
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 1.利用文件上传解析器来判断是否是文件上传请求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
// 如果之前被MultipartFilter包装过了,就不做处理
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
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 {
// 2、利用文件上传解析器来解析文件上传的请求
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;
}
流程:
- 利用
MultipartResolver
(文件上传解析器)来判断是否是文件上传请求:
isMultipart
- 默认使用
StandardServletMultipartResolver
- 默认使用
- 利用
MultipartResolver
(文件上传解析器)来解析文件上传的请求:
resolveMultipart()
- 会把请求包装为
StandardMultipartHttpServletRequest
- 会把请求包装为
这些工作都是利用文件上传解析器来做的,所以我们把文件上传解析器搞明白也就知道了
MultipartResolver(文件上传解析器)
主要作用就是把真实文件包装为
MultipartFile
对象,并缓存起来以便后面封装参数时使用
public interface MultipartResolver {
// 判断请求是否是文件上传的请求
boolean isMultipart(HttpServletRequest request);
// 将请求包装为 StandardMultipartHttpServletRequest
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
// 清除资源
void cleanupMultipart(MultipartHttpServletRequest request);
}
1、
isMultipart()
:判断请求是否是文件上传的请求。其实就是判断
Content-Type
的值是否是以
multipart/form-data
或
multipart/
开头
(这里也就解释了为啥我们发送文件上传的请求时
Content-Type
的值要为
multipart/form-data
)
2、
resolveMultipart()
:将请求包装为
MultipartHttpServletRequest
MultipartResolver
的默认实现是
StandardServletMultipartResolver
,它会把请求封装为
StandardMultipartHttpServletRequest
,把文件封装为
StandardMultipartFile
// 是否延迟解析
private boolean resolveLazily = false;
// 判断
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(),
(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}
// 包装请求
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
MultipartHttpServletRequest(文件上传请求)
默认实现
StandardMultipartHttpServletRequest
,会把文件封装为
StandardMultipartFile
// 创建文件上传请求
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
super(request);
// 是否延迟解析,默认false
if (!lazyParsing) {
// 解析请求
parseRequest(request);
}
}
private void parseRequest(HttpServletRequest request) {
try {
// 从 HttpServletRequest 中获取上传的文件
Collection<Part> parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet<>(parts.size());
// 存储封装好的文件对象
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
// 遍历所有的文件
for (Part part : parts) {
String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
ContentDisposition disposition = ContentDisposition.parse(headerValue);
// 获取文件名字
String filename = disposition.getFilename();
if (filename != null) {
// 添加到集合中
files.add(part.getName(), new StandardMultipartFile(part, filename));
}
else {
// 没有文件名,就是普通参数了
this.multipartParameterNames.add(part.getName());
}
}
// 将上面所有生成的 StandardMultipartFile 文件对象设置到父类的 multipartFiles属性中
// 以便后面封装参数时使用
setMultipartFiles(files);
}
catch (Throwable ex) {
handleParseFailure(ex);
}
}
protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
this.multipartFiles =
new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles));
}
从
HttpServletRequest
中获取上传的文件
Part
遍历所有的文件
从
Part
中获取请求头
Content-Disposition
的值,解析生成
ContentDisposition
对象,然后获取文件名情况1
:文件名不为空,说明是文件,把文件封装为
StandardMultipartFile
对象情况2
:文件名为空,说明是普通参数,则保存参数名称
将上面所有生成的
StandardMultipartFile
文件对象设置到父类的
multipartFiles
属性中,以便后面封装参数时使用
整个执行的原理到这里也就完毕了。
自动配置原理
文件上传的自动配置类是
MultipartAutoConfiguration
@AutoConfiguration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {
private final MultipartProperties multipartProperties;
public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}
@Bean
@ConditionalOnMissingBean(MultipartConfigElement.class)
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
启用文件上传的配置类
MultipartProperties
,配置前缀:
spring.servlet.multipart
- 也就是说我们可以通过这个类,然后在
application.yml
配置文件中来配置默认底层的规则
- 也就是说我们可以通过这个类,然后在
给容器中导入了
MultipartConfigElement
类:文件上传的配置
- 我们可以在容器中自己注册这个类
给容器中导入了文件上传解析器
StandardServletMultipartResolver
,标准的Servlet文件上传解析器,用来处理文件上传
- 设置了
resolveLazily
属性:解析文件是否延迟解析,默认不是延迟解析
- 设置了
我们也可以往容器中注册我们
自定义
的文件上传解析器,SpringBoot就会使用我们的。因为有条件注解来动态判断
总结
执行流程
:利用
StandardServletMultipartResolver
(文件上传解析器)来包装请求、把每一个真实文件封装为
MultipartFile
类,以便我们能简单的操作。还会把所有的
MultipartFile
对象设置到父类的
multipartFiles
属性中,
以便后面封装参数时使用
自动配置
:利用SpringBoot的自动配置往容器中注册一个默认的
文件上传解析器
注意:
文件上传解析器默认全局只能有一个,不能像
HandlerMapping
、
HandlerAdapter
有多个
DispatcherServlet
中就是这么写的
@Nullable
private MultipartResolver multipartResolver;
private List<HandlerMapping> handlerMappings;
/** List of HandlerAdapters used by this servlet. */
@Nullable
private List<HandlerAdapter> handlerAdapters;