每日鸡汤 ——
这短短的一生我们最终都会逝去,你不妨大胆一些,爱一个人,攀一座山,追一个梦。
对文件上传功能的配置都在 MultipartAutoConfiguration.class 中
背景知识:
如何用 IDEA 快速定位到某个类 Ctrl + N 搜索即可
如果用 IDEA 快速定位到类中某个方法 Ctrl + F 搜索即可
@ConditionalOnMissingBean({MultipartResolver.class})
判断容器中无文件上传解析器,若无自动创建
@ConditionalOnMissingBean({MultipartResolver.class}) // 判断注解
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
与文件上传相关功能有关的语句,请见下面的注释:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request; // 判断是否是文件上传请求(封装好的文件上传请求与原请求不相等,则判断是文件上传请求)
mappedHandler = this.getHandler(processedRequest); // 找谁能处理文件上传请求
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
(1)processedRequest = this.checkMultipart(request)
—— 用于判断是否是文件上传请求
—— Step Into 查看:发现使用 multipartResolver
判断是否是文件上传请求
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { // 判断是否是文件上传请求
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
} else if (this.hasMultipartException(request)) {
this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
} else {
try {
return this.multipartResolver.resolveMultipart(request); // 解析文件上传请求
} catch (MultipartException var3) {
if (request.getAttribute("javax.servlet.error.exception") == null) {
throw var3;
}
}
this.logger.debug("Multipart resolution failed for error dispatch", var3);
}
}
return request;
}
StepInto——
multipartResolver
判断是否是文件上传请求方式 —— 用 String 工具类判断是否以 multipart/ 开头(这也解释了为什么我们在写前端表单接收文件时,必须使用multipart)
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
(2)this.multipartResolver.resolveMultipart(request)
解析文件上传请求 ——
其将文件上传请求封装为 MultipartHttpServletRequest
类返回
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
文件请求参数解析器:
(1) InvocableHandlerMethod.class
找到参数解析器,执行文件上传代理
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
RequestPart requestPart = (RequestPart)parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = (requestPart == null || requestPart.required()) && !parameter.isOptional();
String name = this.getPartName(parameter, requestPart);
parameter = parameter.nestedIfOptional();
Object arg = null;
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); // 文件上传代理
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
arg = mpArg;
} else {
try {
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
arg = this.readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(request, arg, name);
if (arg != null) {
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
} catch (MultipartException | MissingServletRequestPartException var13) {
if (isRequired) {
throw var13;
}
}
}
}
(2)确定每个参数的值
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
(3)解析所有参数
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
AbstractMultipartHttpServletRequest.class
public List<MultipartFile> getFiles(String name) {
List<MultipartFile> multipartFiles = (List)this.getMultipartFiles().get(name);
return multipartFiles != null ? multipartFiles : Collections.emptyList();
}
public Map<String, MultipartFile> getFileMap() {
return this.getMultipartFiles().toSingleValueMap();
}
总结:
- 请求进入,使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
- 参数解析器解析请求中的文件内容封装成 MultipartFile
- 将 request 文件封装为一个 Map (Map
)
通过观察源码,可以得到许多 SpringBoot 为我们封装好的文件工具类,如 FileCopyUtils 实现文件流的拷贝