當前位置:網站首頁>文件上傳原理

文件上傳原理

2022-01-27 01:40:55 richest_qi

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解析。
在這裏插入圖片描述

版權聲明
本文為[richest_qi]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201270140552071.html

隨機推薦