SpringMVC源码-接口请求执行流程,包含九大内置组件的实例化初始化,拦截器调用,页面渲染等源码讲解

时间:2024-10-17 10:09:03

一、上传文件功能的实现:

前端JSP代码:
form 表单提交,enctype为multipart/form-data,请求方式POST

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    pageContext.setAttribute("ctx",request.getContextPath());
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${ctx}/fileupload" enctype="multipart/form-data" method="post">
    描述:<input type="text" name="desc"><br><br>
    文件:<input type="file" name="file"><br><br>
    <input type="submit" value="上传">
</form>
</body>
</html>

FileUploadController 的 fileupload方法:

 import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

@Controller
public class FileUploadController {

    @RequestMapping("fileupload")
    public String upload(MultipartFile file, HttpServletRequest request,String desc) throws IOException {
        System.out.println(desc);
        if (file.isEmpty()){
            return "false";
        }
        String path = request.getServletContext().getRealPath("/WEB-INF/file");
        String fileName = file.getOriginalFilename();
        File filePath = new File(path,fileName);
        if (!filePath.getParentFile().exists()){
            filePath.getParentFile().mkdir();
        }
        file.transferTo(filePath);
        return "success";
    }
}

二、上传文件使用的组件

MultipartResolver

MultipartResolver用于处理上传请求,处理方式是将普通的request包装成MultipartHttpServletRequest,可以直接调用getFile方法来获取File,如果上传多个文件,可以调用getFileMap来处理。

public interface MultipartResolver {

	/**
	 * 判断是否是上传请求
	 */
	boolean isMultipart(HttpServletRequest request);

	/**
	 * 将request请求包装成MultipartHttpServletRequest
	 */
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

	/**
	 * 处理完成之后清理上传过程中产生的临时资源
	 */
	void cleanupMultipart(MultipartHttpServletRequest request);

}

类继承关系,需要实现上面三个方法:
在这里插入图片描述

CommonsMultipartResolver

<a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a>

依赖的一个apache的组件jar包

初始化九大内置组件的时候,没有对上传组件设置默认值,所以要提前交给spring生成。
放在SpringMVC的配置文件里,等待加载完成。

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="17367648787"></property>
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>

三、上传文件执行流程

1.检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
在这里插入图片描述

确定请求是否包含多部分内容的实用程序方法。注意:在FileUpload 1.1发布之后,这个方法将被移动到ServletFileUpload类中。不幸的是,由于此方法是静态的,因此在删除此方法之前不可能提供其替代品。

在这里插入图片描述
MULTIPART和enctype=“multipart/form-data” 对应,证明是上传文件的请求。
在这里插入图片描述

this.multipartResolver.isMultipart(request)返回true,

在这里插入图片描述
进入

// 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
return this.multipartResolver.resolveMultipart(request);

CommonsMultipartResolver#resolveMultipart

	// 处理懒加载,如果为true的话,会将解析请求的操作放到DefaultMultipartHttpServletRequest的initializeMultipart方法中,只有在实际调用的时候才会被调用
	// 如果值为false的话,那么会先调用parseRequest方法来处理request请求,然后将处理的结果放到DefaultMultipartHttpServletRequest
	private boolean resolveLazily = false;
	
	public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
		Assert.notNull(request, "Request must not be null");
		if (this.resolveLazily) {
			return new DefaultMultipartHttpServletRequest(request) {
				@Override
				protected void initializeMultipart() {//
					MultipartParsingResult parsingResult = parseRequest(request);
					setMultipartFiles(parsingResult.getMultipartFiles());
					setMultipartParameters(parsingResult.getMultipartParameters());
					setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
				}
			};
		}
		else {
			MultipartParsingResult parsingResult = parseRequest(request);
			return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
					parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
		}
	}

MultipartParsingResult parsingResult = parseRequest(request);

	/**
	 * 对请求进行处理,转成MultipartParsingResult对象
	 *
	 * Parse the given servlet request, resolving its multipart elements.
	 * @param request the request to parse
	 * @return the parsing result
	 * @throws MultipartException if multipart resolution failed.
	 */
	protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
		// 从请求中读出当前请求的编码
		String encoding = determineEncoding(request);
		//按照请求的编码,获取一个FileUpload对象,装载到CommonsFileUploadSupport的property属性都会被装入这个对象中
		//prepareFileUpload是继承自CommonsFileUploadSupport的函数,会比较请求的编码和XML中配置的编码,如果不一样,会拒绝处理
		FileUpload fileUpload = prepareFileUpload(encoding);
		try {
			// 对请求中的multipart文件进行具体的处理
			List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
			return parseFileItems(fileItems, encoding);
		}
		catch (FileUploadBase.SizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
		}
		catch (FileUploadBase.FileSizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
		}
		catch (FileUploadException ex) {
			throw new MultipartException("Failed to parse multipart servlet request", ex);
		}
	}

// 对请求中的multipart文件进行具体的处理
List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);

public List<FileItem> parseRequest(RequestContext ctx)
            throws FileUploadException {
        List<FileItem> items = new ArrayList<FileItem>();
        boolean successful = false;
        try {
            FileItemIterator iter = getItemIterator(ctx);
            FileItemFactory fac = getFileItemFactory();
            if (fac == null) {
                throw new NullPointerException("No FileItemFactory has been set.");
            }
            while (iter.hasNext()) {
                final FileItemStream item = iter.next();
                // Don't use getName() here to prevent an InvalidFileNameException.
                final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
                FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
                                                   item.isFormField(), fileName);
                items.add(fileItem);
                try {
                    Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
                } catch (FileUploadIOException e) {
                    throw (FileUploadException) e.getCause();
                } catch (IOException e) {
                    throw new IOFileUploadException(format("Processing of %s request failed. %s",
                                                           MULTIPART_FORM_DATA, e.getMessage()), e);
                }
                final FileItemHeaders fih = item.getHeaders();
                fileItem.setHeaders(fih);
            }
            successful = true;
            return items;
        } catch (FileUploadIOException e) {
            throw (FileUploadException) e.getCause();
        } catch (IOException e) {
            throw new FileUploadException(e.getMessage(), e);
        } finally {
            if (!successful) {
                for (FileItem fileItem : items) {
                    try {
                        fileItem.delete();
                    } catch (Exception ignored) {
                        // ignored TODO perhaps add to tracker delete failure list somehow?
                    }
                }
            }
        }
    }

获取到传递得文件
在这里插入图片描述

	/**
	 * 对请求进行处理,转成MultipartParsingResult对象
	 *
	 * Parse the given servlet request, resolving its multipart elements.
	 * @param request the request to parse
	 * @return the parsing result
	 * @throws MultipartException if multipart resolution failed.
	 */
	protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
		// 从请求中读出当前请求的编码
		String encoding = determineEncoding(request);
		//按照请求的编码,获取一个FileUpload对象,装载到CommonsFileUploadSupport的property属性都会被装入这个对象中
		//prepareFileUpload是继承自CommonsFileUploadSupport的函数,会比较请求的编码和XML中配置的编码,如果不一样,会拒绝处理
		FileUpload fileUpload = prepareFileUpload(encoding);
		try {
			// 对请求中的multipart文件进行具体的处理
			List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
			return parseFileItems(fileItems, encoding);
		}
		catch (FileUploadBase.SizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
		}
		catch (FileUploadBase.FileSizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
		}
		catch (FileUploadException ex) {
			throw new MultipartException("Failed to parse multipart servlet request", ex);
		}
	}

parseFileItems

	/**
	 * Parse the given List of Commons FileItems into a Spring MultipartParsingResult,
	 * containing Spring MultipartFile instances and a Map of multipart parameter.
	 * @param fileItems the Commons FileItems to parse
	 * @param encoding the encoding to use for form fields
	 * @return the Spring MultipartParsingResult
	 * @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem)
	 */
	protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
		// 保存上传的文件
		MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
		// 保存参数
		Map<String, String[]> multipartParameters = new HashMap<>();
		// 保存参数的contentType
		Map<String, String> multipartParameterContentTypes = new HashMap<>();

		// Extract multipart files and multipart parameters.
		// 将fileItems分为文件和参数两类,并设置到对应的map
		for (FileItem fileItem : fileItems) {
			// 如果是参数类型
			if (fileItem.isFormField()) {
				String value;
				String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
				try {
					value = fileItem.getString(partEncoding);
				}
				catch (UnsupportedEncodingException ex) {
					if (logger.isWarnEnabled()) {
						logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
								"' with encoding '" + partEncoding + "': using platform default");
					}
					value = fileItem.getString();
				}
				String[] curParam = multipartParameters.get(fileItem.getFieldName());
				if (curParam == null) {
					// simple form field
					// 单个参数
					multipartParameters.put(fileItem.getFieldName(), new String[] {value});
				}
				else {
					// array of simple form fields
					// 数组参数
					String[] newParam = StringUtils.addStringToArray(curParam, value);
					multipartParameters.put(fileItem.getFieldName(), newParam);
				}
				// 保存参数的contentType
				multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
			}
			else {
				// multipart file field
				// 如果是文件类型
				CommonsMultipartFile file = createMultipartFile(fileItem);
				multipartFiles.add(file.getName(), file);
				LogFormatUtils.traceDebug(logger, traceOn ->
						"Part '" + file.getName() + "', size " + file.getSize() +
								" bytes, filename='" + file.getOriginalFilename() + "'" +
								(traceOn ? ", storage=" + file.getStorageDescription() : "")
				);
			}
		}
		return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
	}

parseFileItems得调用堆栈:

parseFileItems:301, CommonsFileUploadSupport (org.springframework.web.multipart.commons)
parseRequest:168, CommonsMultipartResolver (org.springframework.web.multipart.commons)
resolveMultipart:145, CommonsMultipartResolver (org.springframework.web.multipart.commons)
checkMultipart:1286, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1088, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doPost:971, FrameworkServlet (org.springframework.web.servlet)
service:681, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core