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

SpringMVC原理(1)-文件上传请求的更多相关文章

  1. springMVC + hadoop + httpclient 文件上传请求直接写入hdfs

    1.首先是一个基于httpclient的java 应用程序,代码在这篇文章的开头:点击打开链接 2.我们首先写一个基于springMVC框架的简单接收请求上传的文件保存本地文件系统的demo,程序代码 ...

  2. Hadoop之HDFS原理及文件上传下载源码分析(上)

    HDFS原理 首先说明下,hadoop的各种搭建方式不再介绍,相信各位玩hadoop的同学随便都能搭出来. 楼主的环境: 操作系统:Ubuntu 15.10 hadoop版本:2.7.3 HA:否(随 ...

  3. Hadoop之HDFS原理及文件上传下载源码分析(下)

    上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文 ...

  4. 使用SpringMVC框架实现文件上传和下载功能

    使用SpringMVC框架实现文件上传和下载功能 (一)单个文件上传 ①配置文件上传解释器 <!—配置文件上传解释器 --> <mvc:annotation-driven>&l ...

  5. (转)SpringMVC学习(九)——SpringMVC中实现文件上传

    http://blog.csdn.net/yerenyuan_pku/article/details/72511975 这一篇博文主要来总结下SpringMVC中实现文件上传的步骤.但这里我只讲单个文 ...

  6. springMVC实现多文件上传

    <h2>上传多个文件 实例</h2> <form action="/workreport/uploadMultiFile.html" method=& ...

  7. SpringMVC国际化与文件上传

    点击阅读上一章 其实SpringMVC中的页面国际化与上一章的验证国际化基本一致. 1.对页面进行国际化 1)首先我们对Spring配置文件中添加国际化bean配置 <!-- 注册国际化信息,必 ...

  8. SpringMVC框架06——文件上传与下载

    1.文件上传 Spring MVC框架的文件上传是基于commons-fileupload组件的文件上传,只不过Spring MVC框架在原有文件上传组件上做了进一步封装,简化了文件上传的代码实现. ...

  9. 再springMVC中自定义文件上传处理解决与原spring中MultipartResolve冲突问题

    相信很多朋友再用springmvc时都遇见了一个问题,那就是自带的获取上传的东西太慢,而且不知道如何修改,其实不然,spring框架既然给我们开放了这个接口,就一定遵从了可扩展性的原则,经过查看org ...

  10. 【SpringMVC学习08】SpringMVC中实现文件上传

    之前有写过一篇struts2实现的文件上传,这一篇博文主要来总结下springmvc实现文件上传的步骤.首先来看一下单个文件的上传,然后再来总结下多个文件上传. 1. 环境准备 springmvc上传 ...

随机推荐

  1. ABAP 7.58 中支持任意精度算术的新类

    1. 引言 通常,有两种对编程语言的改进.第一种是让困难的事情变得简单,第二种是让不可能的事情变为可能.本文介绍的是任意精度算术,它属于第二类:使在ABAP中原本不可能的事情成为可能. 过去已经可以在 ...

  2. Ubuntu 安装谷歌中文输入法

    Ubuntu 安装谷歌中文输入法 下载谷歌拼音:sudo apt-get install fcitx-googlepinyin 点击设置: 第一次打开需要点击安装: 设置完成后重启系统 点击右上角键盘 ...

  3. gin-vue-admin 03 项目打包上线

    目录 作者视频 思路 环境要求 1. 配置nginx 2.打包前台vue代码 3.打包后台go代码 4. 上传代码到服务器 5. 后台运行power 6. 访问后台 开发场景: 1. nginx 配置 ...

  4. ARM64: ARDP

    1 指令语法 ardp <Xd>, <lable> 2 指令语义 1 获取程序计数器PC寄存器的值: 2 将PC寄存器值的低12位全部取0; 3 将lable的值乘以4096, ...

  5. C数据结构:循环队列的顺序存储结构

    顺序队列目录 队列的定义 定义 假溢出 空间浪费的缺点 如何解决 循环队列的缺点 *==主要的算法思想(重要)==* 如何理解循环队列(必看) 结构体代码 两种实现方法 **①循环队列,队头和队尾指针 ...

  6. linux文件的三个时间,修改文件时间为任意时间

    目录 一.文件的三个时间 二.修改文件的三种时间为任意时间 一.文件的三个时间 当我们在linux中创建了文件或文件夹,文件/文件夹就有了时间属性,而且linux中的文件具有三个时间,可以通过stat ...

  7. HTML——标签元素的两大类

    块级标签(block) – 独占一行 内联标签(inline) – 按文本内容占位 div标签和span标签 <div>只是一个块级元素,并无实际的意义.主要通过CSS样式为其赋予不同的表 ...

  8. 基于 Kubernetes 的 CICD 基础设施即代码

    在上一篇基于 Kubernetes 的基础设施即代码一文中,我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊使用的基础设施是如何使用代码描述的,以 ...

  9. itest(爱测试) 4.5.1 发布,开源BUG 跟踪管理 & 敏捷测试管理软件

    本次发布一共6个更新(其中一个4.5.0的重大BUG,不得不先发布4.5.1).4.5.0中增加ldap 登录支持时,引入一个BUG,新增的itest本地用户不能登录,除非重启.  V4.5.1详情如 ...

  10. cookie cookie的获取

       什么是 cookie                  cookie 是 浏览器 的 一种功能                 是 浏览器 用来 存储 前端数据的一种 存储机制          ...