Spring MVC系列(15)-文件上传流程源码分析

前言

在上篇文档,我们分析了如何使用Spring MVC进行文件的上传和下载,也分析了一些常用的类和接口,那么这些请求和解析器是如何工作的呢?

核心类

Part接口

Part是javax.servlet.http包下的一个接口,servlet3.0加入,此类用于封装multipart/form-data请求时,上传文件的请求体。

public interface Part {
   
     
    InputStream getInputStream() throws IOException;

    String getContentType();

    String getName();

    String getSubmittedFileName();

    long getSize();

    void write(String var1) throws IOException;

    void delete() throws IOException;

    String getHeader(String var1);

    Collection<String> getHeaders(String var1);

    Collection<String> getHeaderNames();
}

ApplicationPart

ApplicationPart是Tomcat中的类,实现了Part接口,是文件上传时,会将文件封装为此对象。

它有两个重要的属性:

    private final FileItem fileItem;
    private final File location;

FileItem封装了文件信息。
 
location表示文件实际对象,当上传文件时,会先存放到Tomcat的临时目录。

C:\Users\Administrator\AppData\Local\Temp\tomcat.9111.2463429038111242355\work\Tomcat\localhost\ROOT\upload_4b2ad82c_e0c0_45f3_8913_c2daa6234881_00000006.tmp

源码分析

1. 检查Multipart

所有的请求都由DispatcherServlet来调度,所以首先还是进入到其doDispatch方法。

doDispatch首先会进入到checkMultipart方法。
 
checkMultipart会调用DispatcherServlet的组件多部分请求解析器StandardServletMultipartResolver进行解析。

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
   
     
    	// 1. 解析器存在,并且是multipart请求,也就是contentType包含了multipart/
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
   
     
        	// 2. 检查Request 能不能转化为MultipartHttpServletRequest
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
   
     
                if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
   
     
                }
            // 3. 检查是否有MultipartException
            } else if (this.hasMultipartException(request)) {
   
     
               
            } else {
   
     
                try {
   
     
                	// 4. 解析器解析请求
                    return this.multipartResolver.resolveMultipart(request);
                } catch (MultipartException var3) {
   
                    
            }
        }
        return request;
    }

2. 创建StandardMultipartHttpServletRequest对象

在第一步中,是multipart请求,则会进入到解析器中。

StandardServletMultipartResolver的机械方法,会直接new一个StandardMultipartHttpServletRequest请求对象。

    public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
   
     
        return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
    }

StandardMultipartHttpServletRequest的构造方法中,如果没有配置延迟解析,直接进入到parseRequest方法解析请求。

    public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
   
     
        super(request);
        if (!lazyParsing) {
   
     
            this.parseRequest(request);
        }
    }

3. 获取Parts

parseRequest方法di异步会从request中获取Part对象。

这里面的逻辑比较复杂,代码也很多,大概就是读取的 tomcat 放到服务器上的临时文件的文件流,获取这个文件的信息,并转为相关的文件信息对象FileItem 和文件对象。
 
最后这里的Parts包含了以下信息。
 

4. parseRequest

parseRequest方法主要是,将一些文件信息,设置到请求对象中。

	// 解析请求
	private void parseRequest(HttpServletRequest request) {
   
     
		try {
   
     
			// 1. request.getParts(),用于获取使用multipart/form-data格式传递的http请求的请求体,通常用于获取上传文件信息。
			Collection<Part> parts = request.getParts();
			// 2. 创建multipart参数的名称集合,MultipartFile对象集合
			this.multipartParameterNames = new LinkedHashSet<>(parts.size());
			MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
			// 3. 循环文件信息,并保存到当前请求StandardMultipartHttpServletRequest对象中
			for (Part part : parts) {
   
     
				// 从消息头Content-Disposition获取 文件描述 form-data; name="file"; filename="01053230.zip"
				String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
				// 解析消息头
				ContentDisposition disposition = ContentDisposition.parse(headerValue);
				// 获取文件名
				String filename = disposition.getFilename();
				if (filename != null) {
   
     
					// 文件名包含=? ,编码处理
					if (filename.startsWith("=?") && filename.endsWith("?=")) {
   
     
						filename = MimeDelegate.decode(filename);
					}
					// 4. 添加文件名= MultipartFile对象,键值对,放入到MultiValueMap<String, MultipartFile> 集合中
					files.add(part.getName(), new StandardMultipartFile(part, filename));
				}
				else {
   
     
					// 5. 当前类添加multipart 参数集合
					this.multipartParameterNames.add(part.getName());
				}
			}
			//5.  MultiValueMap<String, MultipartFile> 集合设置到当前类中
			setMultipartFiles(files);
		}
		catch (Throwable ex) {
   
     
			handleParseFailure(ex);
		}
	}

经过检查解析,最终我们的请求对象变为了StandardMultipartHttpServletRequest,其中封装了文件信息。
 

4. 执行控制器方法

doDispatch接着往下执行,进入到控制器执行器,开始执行控制器方法。
 

经过其他组件处理,就到了我们的上传接口中。控制器接收到我们的文件对象,往下执行就完成了上传文件的流程。
 

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: