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

springmvc · 浏览次数 : 0

小编点评

本文主要介绍了Spring Boot中的文件上传功能及其实现原理。通过分析DispatcherServlet类的doDispatch()方法,我们可以了解到文件上传请求的处理过程。同时,文章还探讨了文件上传自动配置原理和执行流程。 1. **文件上传请求处理**: - Spring Boot通过MultipartResolver(文件上传解析器)来判断请求是否为文件上传请求。 - 使用StandardServletMultipartResolver解析文件上传请求,并将文件封装为StandardMultipartFile对象。 - 将所有封装好的MultipartFile对象设置到父类的multipartFiles属性中,以便后续封装参数时使用。 2. **自动配置原理**: - Spring Boot提供了MultipartAutoConfiguration类,用于自动配置文件上传功能。 - 通过ConditionalOnClass、ConditionalOnProperty等注解来实现自动装配。 - 注册默认的文件上传解析器和MultipartConfigElement类。 - 提供了`multipart-config`元素来配置文件上传的默认规则。 3. **文件上传配置**: - 可以在`application.yml`中配置文件上传相关的参数,如`spring.servlet.multipart`前缀。 - 可以自定义文件上传解析器,并通过`@Bean`注解将其注册到容器中。 - 设置`resolveLazily`属性来控制文件上传解析器的延迟解析行为。 总的来说,Spring Boot通过一系列注解和自动配置,简化了文件上传功能的实现,使得开发者能够更加便捷地处理文件上传请求。

正文

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

与SpringMVC源码(1)-文件上传请求相似的内容:

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

我们文件上传接口只需要在方法参数上写MultipartFile类,mvc就可以帮我们把上传的文件封装为这个类的对 象供我们非常方便的操作,那它是怎么做的呢?我们一起来看看 我们发的请求默认都是由DispatcherServlet类的doDispatch()来处理,这个方法的逻辑处理的第一步就是处理文

Spring源码核心剖析

SpringAOP作为Spring最核心的能力之一,其重要性不言而喻。然后需要知道的是AOP并不只是Spring特有的功能,而是一种思想,一种通用的功能。而SpringAOP只是在AOP的基础上将能力集成到SpringIOC中,使其作为bean的一种,从而我们能够很方便的进行使用。

一文了解 - -> SpringMVC

一、SpringMVC概述 Spring MVC 是由Spring官方提供的基于MVC设计理念的web框架。 SpringMVC是基于Servlet封装的用于实现MVC控制的框架,实现前端和服务端的交互。 1.1 SpringMVC优势 严格遵守了MVC分层思想 采用了松耦合、插件式结构;相比较于我

SpringMVC-01-回顾MVC架构

1、什么是MVC MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件架构模式。 它通过将业务逻辑、页面控制、显示视图分离的方法来组织代码。 主要作用是降低了视图与业务逻辑间的双向偶合。 它不是一种设计模式,而是一种架构模式。当然不同的MVC存在差异。 Mo

初识 SpringMVC,运行配置第一个Spring MVC 程序

1. 初识 SpringMVC,运行配置第一个Spring MVC 程序 @目录1. 初识 SpringMVC,运行配置第一个Spring MVC 程序1.1 什么是 MVC2. Spring MVC 概述2.1 Spring MVC 的作用:3. 运行配置第一个 Spring MVC 程序3.1

这问题巧了,SpringMVC 不同参数处理机制引发的思考

这个问题非常有趣,不是SpringMVC 的问题,是实际开发中混合使用了两种请求方式暴露出来的。

SSM(Spring+SpringMVC+MyBatis)框架集成

进行SSM(Spring+SpringMVC+MyBatis)集成的主要原因是为了提高开发效率和代码可维护性。SSM是一套非常流行的Java Web开发框架,它集成了Spring框架、SpringMVC框架和MyBatis框架,各自发挥优势,形成了一个完整的开发框架。

一分钟学会、三分钟上手、五分钟应用,快速上手责任链框架详解 | 京东云技术团队

责任链模式是开发过程中常用的一种设计模式,在SpringMVC、Netty等许多框架中均有实现。我们日常的开发中如果要使用责任链模式,通常需要自己来实现,但自己临时实现的责任链既不通用,也很容易产生框架与业务代码耦合不清的问题,增加Code Review 的成本。

Spring MVC 中使用 RESTFul 编程风格

1. Spring MVC 中使用 RESTFul 编程风格 @目录1. Spring MVC 中使用 RESTFul 编程风格2. RESTFul 编程风格2.1 RESTFul 是什么2.2 RESTFul风格与传统方式对比3. Spring MVC 中使用 RESTFul 编程风格(增删改查)

Spring MVC 获取三个域(request请求域,session 会话域,application 应用域)对象的方式

1. Spring MVC 获取三个域(request请求域,session 会话域,application 应用域)对象的方式 @目录1. Spring MVC 获取三个域(request请求域,session 会话域,application 应用域)对象的方式2. Servlet中的三个域对象3