SpringMVC源码(1)-文件上传请求

cnblogs 2024-06-29 17:09:00 阅读 58

我们文件上传接口只需要在方法参数上写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;

}

流程:

  1. 利用 MultipartResolver文件上传解析器)来判断是否是文件上传请求:isMultipart
    1. 默认使用StandardServletMultipartResolver
  2. 利用 MultipartResolver文件上传解析器)来解析文件上传的请求:resolveMultipart()
    1. 会把请求包装为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-datamultipart/ 开头

(这里也就解释了为啥我们发送文件上传的请求时 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));

}

  1. HttpServletRequest 中获取上传的文件 Part

  2. 遍历所有的文件

    1. Part 中获取请求头Content-Disposition的值,解析生成ContentDisposition对象,然后获取文件名

    2. 情况1:文件名不为空,说明是文件,把文件封装为StandardMultipartFile对象

    3. 情况2:文件名为空,说明是普通参数,则保存参数名称

  3. 将上面所有生成的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;

}

}

  1. 启用文件上传的配置类MultipartProperties,配置前缀:spring.servlet.multipart

    1. 也就是说我们可以通过这个类,然后在application.yml配置文件中来配置默认底层的规则
  2. 给容器中导入了MultipartConfigElement类:文件上传的配置

    1. 我们可以在容器中自己注册这个类
  3. 给容器中导入了文件上传解析器StandardServletMultipartResolver,标准的Servlet文件上传解析器,用来处理文件上传

    1. 设置了resolveLazily属性:解析文件是否延迟解析,默认不是延迟解析

我们也可以往容器中注册我们自定义的文件上传解析器,SpringBoot就会使用我们的。因为有条件注解来动态判断

总结

执行流程:利用 StandardServletMultipartResolver(文件上传解析器)来包装请求、把每一个真实文件封装为

MultipartFile类,以便我们能简单的操作。还会把所有的MultipartFile对象设置到父类的multipartFiles

属性中,以便后面封装参数时使用

自动配置:利用SpringBoot的自动配置往容器中注册一个默认的文件上传解析器

注意:文件上传解析器默认全局只能有一个,不能像 HandlerMappingHandlerAdapter 有多个

DispatcherServlet中就是这么写的

@Nullable

private MultipartResolver multipartResolver;

private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet. */

@Nullable

private List<HandlerAdapter> handlerAdapters;



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。