文件上传原理

MultipartAutoConfiguration自动配置文件上传解析器

类MultipartAutoConfiguration中自动配置了文件上传解析器StandardServletMultipartResolver ,如下:

@Configuration(proxyBeanMethods = false)
@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, CommonsMultipartResolver.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;
	}
}

如果容器中没有MultipartResolver类实例(MultipartResolver.class),则调用方法multipartResolver,该方法会返回一个StandardServletMultipartResolver类实例,且实例名为multipartResolver,(@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)),并将该实例放入容器,即得到了文件上传解析器。
如果容器中有MultipartResolver类实例,则不会调用方法multipartResolver。
SpringBoot通过底层的这种自动配置保证了容器中一定会有一个MultipartResolver类实例(文件上传解析器),从而支持文件上传功能。

文件上传解析器的自定义配置

文件上传解析器的自定义配置,通过以下方式实现,如spring.servlet.multipart.enabledspring.servlet.multipart.max-file-sizespring.servlet.multipart.max-request-size等。

  1. spring.servlet.multipart为前缀。
@ConditionalOnProperty(prefix = "spring.servlet.multipart",)
  1. 与类MultipartProperties的属性绑定。
public class MultipartProperties {
	private boolean enabled = true;
	private String location;
	private DataSize maxFileSize = DataSize.ofMegabytes(1);
	private DataSize maxRequestSize = DataSize.ofMegabytes(10);
	private DataSize fileSizeThreshold = DataSize.ofBytes(0);
	private boolean resolveLazily = false;
	//...
}

MultipartFile参数解析过程

DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	boolean multipartRequestParsed = false;
	try {
		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);

			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
		}
		else {
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}
DispatcherServlet#checkMultipart

文件上传解析器对请求进行判断,如果请求是文件上传请求(如Content-Type为multipart/form-data),则封装该请求为StandardMultipartHttpServletRequest类请求,且请求中的内容封装为MultipartFile类。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		return this.multipartResolver.resolveMultipart(request);
	}
}
StandardServletMultipartResolver#isMultipart

判断请求是不是文件上传请求,如果请求的Content-Type以multipart/开头,则确定该请求为文件上传请求。(这也是为什么上传文件的表单,一定要加enctype="multipart/form-data"。)

public boolean isMultipart(HttpServletRequest request) {
	return StringUtils.startsWithIgnoreCase(request.getContentType(),
			(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}
StandardServletMultipartResolver#resolveMultipart

如果请求是文件上传请求,则解析该请求,将请求封装为StandardMultipartHttpServletRequest类请求,且将请求中的内容封装为MultipartFile类。

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

//StandardMultipartHttpServletRequest#StandardMultipartHttpServletRequest(javax.servlet.http.HttpServletRequest, boolean)
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
		throws MultipartException {

	super(request);
	if (!lazyParsing) {
		parseRequest(request);
	}
}
//StandardMultipartHttpServletRequest#parseRequest
private void parseRequest(HttpServletRequest request) {
	try {
		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) {
				if (filename.startsWith("=?") && filename.endsWith("?=")) {
					filename = MimeDelegate.decode(filename);
				}
				files.add(part.getName(), new StandardMultipartFile(part, filename));
			}
			else {
				this.multipartParameterNames.add(part.getName());
			}
		}
		setMultipartFiles(files);
	}
	catch (Throwable ex) {
		handleParseFailure(ex);
	}
}
AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {

	return handleInternal(request, response, (HandlerMethod) handler);
}

//RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	ModelAndView mav;
	checkRequest(request);
	mav = invokeHandlerMethod(request, response, handlerMethod);
	return mav;
}

//RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	return getModelAndView(mavContainer, modelFactory, webRequest);
}

//ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

//InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {

	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	return doInvoke(args);
}

//InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	MethodParameter[] parameters = getMethodParameters();
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
	}
	return args;
}

//HandlerMethodArgumentResolverComposite#resolveArgument
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

//RequestPartMethodArgumentResolver#resolveArgument
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
		String name = getPartName(parameter, requestPart);
		parameter = parameter.nestedIfOptional();
		Object arg = null;
		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
		if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
			arg = mpArg;
		}
		return adaptArgumentIfNecessary(arg, parameter);
}

//MultipartResolutionDelegate#resolveMultipartArgument
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
		throws Exception {
		MultipartHttpServletRequest multipartRequest =
				WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
		boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
		if (MultipartFile.class == parameter.getNestedParameterType()) {
			return multipartRequest.getFile(name);
		}
		else if (isMultipartFileArray(parameter)) {
			List<MultipartFile> files = multipartRequest.getFiles(name);
			return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
		}
		
}

MultipartFile是一个接口类,其定义如下,

public interface MultipartFile extends InputStreamSource {
	String getName();
	
	@Nullable
	String getOriginalFilename();
	
	@Nullable
	String getContentType();
	
	boolean isEmpty();
	long getSize();
	byte[] getBytes() throws IOException;
	
	@Override
	InputStream getInputStream() throws IOException;
	
	default Resource getResource() {
		return new MultipartFileResource(this);
	}
	void transferTo(File dest) throws IOException, IllegalStateException;
	default void transferTo(Path dest) throws IOException, IllegalStateException {
		FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
	}

}

MultipartFile类数由参数解析器RequestPartMethodArgumentResolver解析。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值