SpringMVC原理(1)-文件上传请求

打印 上一主题 下一主题

主题 957|帖子 957|积分 2871

我们文件上传接口只需要在方法参数上写MultipartFile类,mvc就可以帮我们把上传的文件封装为这个类的对
象供我们非常方便的操作,那它是怎么做的呢?我们一起来看看
我们发的请求默认都是由DispatcherServlet类的doDispatch()来处理,这个方法的逻辑处理的第一步就是处理文件上传的请求,我们一起来看看是怎么处理的吧。
本文分析的问题:文件上传请求的执行原理、文件上传自动配置原理
执行流程原理

checkMultipart()-处理文件上传的请求

processedRequest = checkMultipart(request):处理文件上传请求。所以我们把这个方法看明白就知道了
  1. @Nullable
  2. // 文件上传解析器,只能有一个
  3. private MultipartResolver multipartResolver;
  4. protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
  5.     // 1.利用文件上传解析器来判断是否是文件上传请求
  6.     if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
  7.         // 如果之前被MultipartFilter包装过了,就不做处理
  8.         if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
  9.             if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
  10.                 logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
  11.             }
  12.         }
  13.         // 是否有异常
  14.         else if (hasMultipartException(request)) {
  15.             logger.debug("Multipart resolution previously failed for current request - " +
  16.                          "skipping re-resolution for undisturbed error rendering");
  17.         }
  18.         else {
  19.             try {
  20.                 // 2、利用文件上传解析器来解析文件上传的请求
  21.                 return this.multipartResolver.resolveMultipart(request);
  22.             }
  23.             catch (MultipartException ex) {
  24.                 if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
  25.                     logger.debug("Multipart resolution failed for error dispatch", ex);
  26.                     // Keep processing error dispatch with regular request handle below
  27.                 }
  28.                 else {
  29.                     throw ex;
  30.                 }
  31.             }
  32.         }
  33.     }
  34.     // If not returned before: return original request.
  35.     return request;
  36. }
复制代码
流程:

  • 使用 MultipartResolver(文件上传解析器)来判断是否是文件上传请求:isMultipart

    • 默认使用StandardServletMultipartResolver

  • 使用 MultipartResolver(文件上传解析器)来解析文件上传的请求:resolveMultipart()

    • 会把请求包装为StandardMultipartHttpServletRequest

这些工作都是使用文件上传解析器来做的,所以我们把文件上传解析器搞明白也就知道了
继承图:

MultipartResolver(文件上传解析器)

主要作用就是把真实文件包装为MultipartFile对象,并缓存起来以便后面封装参数时使用
  1. public interface MultipartResolver {
  2.     // 判断请求是否是文件上传的请求
  3.         boolean isMultipart(HttpServletRequest request);
  4.     // 将请求包装为 StandardMultipartHttpServletRequest
  5.         MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
  6.     // 清除资源
  7.         void cleanupMultipart(MultipartHttpServletRequest request);
  8. }
复制代码
1、 isMultipart():判断请求是否是文件上传的请求。其实就是判断 Content-Type 的值是否是以
multipart/form-datamultipart/ 开头
(这里也就解释了为啥我们发送文件上传的请求时 Content-Type的值要为 multipart/form-data
2、resolveMultipart():将请求包装为MultipartHttpServletRequest
MultipartResolver 的默认实现是 StandardServletMultipartResolver,它会把请求封装为StandardMultipartHttpServletRequest,把文件封装为StandardMultipartFile
  1. // 是否延迟解析
  2. private boolean resolveLazily = false;
  3. // 判断
  4. public boolean isMultipart(HttpServletRequest request) {
  5.     return StringUtils.startsWithIgnoreCase(request.getContentType(),
  6.                                             (this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
  7. }
  8. // 包装请求
  9. public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
  10.     return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
  11. }
复制代码
MultipartHttpServletRequest(文件上传请求)

默认实现StandardMultipartHttpServletRequest,会把文件封装为StandardMultipartFile
  1. // 创建文件上传请求
  2. public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
  3.     throws MultipartException {
  4.     super(request);
  5.     // 是否延迟解析,默认false
  6.     if (!lazyParsing) {
  7.         // 解析请求
  8.         parseRequest(request);
  9.     }
  10. }
  11. private void parseRequest(HttpServletRequest request) {
  12.     try {
  13.         // 从 HttpServletRequest 中获取上传的文件
  14.         Collection<Part> parts = request.getParts();
  15.         this.multipartParameterNames = new LinkedHashSet<>(parts.size());
  16.         // 存储封装好的文件对象
  17.         MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
  18.         // 遍历所有的文件
  19.         for (Part part : parts) {
  20.             String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
  21.             ContentDisposition disposition = ContentDisposition.parse(headerValue);
  22.             // 获取文件名字
  23.             String filename = disposition.getFilename();
  24.             if (filename != null) {
  25.                 // 添加到集合中
  26.                 files.add(part.getName(), new StandardMultipartFile(part, filename));
  27.             }
  28.             else {
  29.                 // 没有文件名,就是普通参数了
  30.                 this.multipartParameterNames.add(part.getName());
  31.             }
  32.         }
  33.         // 将上面所有生成的 StandardMultipartFile 文件对象设置到父类的 multipartFiles属性中
  34.         // 以便后面封装参数时使用
  35.         setMultipartFiles(files);
  36.     }
  37.     catch (Throwable ex) {
  38.         handleParseFailure(ex);
  39.     }
  40. }
  41. protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
  42.     this.multipartFiles =
  43.         new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles));
  44. }
复制代码

  • 从 HttpServletRequest 中获取上传的文件 Part
  • 遍历所有的文件

    • 从Part 中获取请求头Content-Disposition的值,解析生成ContentDisposition对象,然后获取文件名
    • 情况1:文件名不为空,阐明是文件,把文件封装为StandardMultipartFile对象
    • 情况2:文件名为空,阐明是普通参数,则生存参数名称

  • 将上面所有生成的StandardMultipartFile文件对象设置到父类的multipartFiles属性中,以便后面封装参数时使用
整个执行的原理到这里也就完毕了。
自动配置原理

文件上传的自动配置类是MultipartAutoConfiguration
  1. @AutoConfiguration
  2. @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
  3. @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
  4. @ConditionalOnWebApplication(type = Type.SERVLET)
  5. @EnableConfigurationProperties(MultipartProperties.class)
  6. public class MultipartAutoConfiguration {
  7.         private final MultipartProperties multipartProperties;
  8.         public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
  9.                 this.multipartProperties = multipartProperties;
  10.         }
  11.         @Bean
  12.         @ConditionalOnMissingBean(MultipartConfigElement.class)
  13.         public MultipartConfigElement multipartConfigElement() {
  14.                 return this.multipartProperties.createMultipartConfig();
  15.         }
  16.         @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
  17.         @ConditionalOnMissingBean(MultipartResolver.class)
  18.         public StandardServletMultipartResolver multipartResolver() {
  19.                 StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
  20.                 multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
  21.                 return multipartResolver;
  22.         }
  23. }
复制代码

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

    • 也就是说我们可以通过这个类,然后在application.yml配置文件中来配置默认底层的规则

  • 给容器中导入了MultipartConfigElement类:文件上传的配置

    • 我们可以在容器中自己注册这个类

  • 给容器中导入了文件上传解析器StandardServletMultipartResolver,标准的Servlet文件上传解析器,用来处理文件上传

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

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

执行流程:使用 StandardServletMultipartResolver(文件上传解析器)来包装请求、把每一个真实文件封装为MultipartFile类,以便我们能简单的操作。还会把所有的MultipartFile对象设置到父类的multipartFiles属性中,以便后面封装参数时使用
自动配置:使用SpringBoot的自动配置往容器中注册一个默认的文件上传解析器
注意:文件上传解析器默认全局只能有一个,不能像 HandlerMapping、HandlerAdapter 有多个
DispatcherServlet中就是这么写的
  1.         @Nullable
  2.         private MultipartResolver multipartResolver;
  3.         private List<HandlerMapping> handlerMappings;
  4.         /** List of HandlerAdapters used by this servlet. */
  5.         @Nullable
  6.         private List<HandlerAdapter> handlerAdapters;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表