文件上传原理 入口首先查看MultipartAutoConfiguration类,该类中定义SpringBoot已经帮我们配置好的一系列文件上传相关的操作
实验Controller
@ResponseBody
@PostMapping("/upload")
public String upload(@RequestPart MultipartFile file) {
try {
FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(new File("E:\\java_project\\integration-project\\spring-tutorial\\src\\main\\resources\\test\\" + file.getOriginalFilename())));
} catch (IOException e) {
e.printStackTrace();
}
return "update success";
}
在该自动配置类中核心配置了StandardServletMultipartResolver 来处理文件上传逻辑,文件上传相关的配置MultipartProperties这个配置类中,对应yaml文件中的spring.servlet.multipart配置项
@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;
}
}
文件上传原理需要分析StandardServletMultipartResolver类
原理步骤
-
进入DispatcherServlet#doDispatch方法,检查是否是Multipart请求
-
底层调用了multipartResolver的isMultipart方法判断是不是文件上传请求 isMultipart方法逻辑也很简单 就是判断一下请求头中的ContentType是不是以multipart/开头
@Override public boolean isMultipart(HttpServletRequest request) { return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/"); }
-
如果是multipart上传请求 调用 resolver的 resolveMultipart方法将原生的Request包装成一个上传的Request
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); }
-
底层StandardMultipartHttpServletRequest 构造器里面有一个parseRequest方法 里面封装了将request域中的数据封装到一个Map中
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); // 是否懒解析 默认false 进入parseRequest方法核心 if (!lazyParsing) { parseRequest(request); } }
private void parseRequest(HttpServletRequest request) { try { // 原生request的getParts方法 Collection<Part> parts = request.getParts(); // 保存所有的文件名 this.multipartParameterNames = new LinkedHashSet<>(parts.size()); // 一个Map保存了 文件名为K 内容为V的数据 MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size()); // 遍历parts中的对象 将所有对象封装到该 map中 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()); } } // 循环完毕之后 将这个Map 设置到该对象multipartFiles属性中,供后面的方法可以使用 setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } }
-
经过上面的步骤将Part文件包装到了MultipleRequest的包装类中的Map中,然后进入handlerAdapter的handler方法跟之前逻辑类似,先找所有的argumentResolvers,调用supportsParameter判断是否支持该类型的参数解析器,我们找到RequestPartMethodArgumentResolver来处理被@RequestPart标记的方法
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; 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) { continue; } // 遍历所有argumentsResolver 获取能处理解析器 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { // 找到 RequestPartMethodArgumentResolver 处理 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
-
进去该参数解析器中的resolveArgument方法来解析参数, 核心在于调用了MultipartResolutionDelegate.resolveMultipartArgument方法来解析文件上传请求
@Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 拿到原生Request请求 HttpServletRequest servletRequest = 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 = getPartName(parameter, requestPart); parameter = parameter.nestedIfOptional(); Object arg = null; // 核心在于这段代码 调用了MultipartResolutionDelegate代理类的resolveMultipartArgument方法 Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { arg = mpArg; } else { try { HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name); arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType()); if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(request, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } } catch (MissingServletRequestPartException | MultipartException ex) { if (isRequired) { throw ex; } } } if (arg == null && isRequired) { if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { throw new MultipartException("Current request is not a multipart request"); } else { throw new MissingServletRequestPartException(name); } } return adaptArgumentIfNecessary(arg, parameter); }
-
查看MultipartResolutionDelegate#resolveMultipartArgument方法 各种判断不同类型不同的处理方式
@Nullable 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()) { if (multipartRequest == null && isMultipart) { multipartRequest = new StandardMultipartHttpServletRequest(request); } // 因为之前new StandardMultipartHttpServletRequest 已经做了parse操作 此时这个multipartRequest中的file已经封装了文件相关内容,只需要去内部的Map中拿执行的文件名曲取即可 return (multipartRequest != null ? multipartRequest.getFile(name) : null); } else if (isMultipartFileCollection(parameter)) { if (multipartRequest == null && isMultipart) { multipartRequest = new StandardMultipartHttpServletRequest(request); } return (multipartRequest != null ? multipartRequest.getFiles(name) : null); } else if (isMultipartFileArray(parameter)) { if (multipartRequest == null && isMultipart) { multipartRequest = new StandardMultipartHttpServletRequest(request); } if (multipartRequest != null) { List<MultipartFile> multipartFiles = multipartRequest.getFiles(name); return multipartFiles.toArray(new MultipartFile[0]); } else { return null; } } else if (Part.class == parameter.getNestedParameterType()) { return (isMultipart ? request.getPart(name): null); } else if (isPartCollection(parameter)) { return (isMultipart ? resolvePartList(request, name) : null); } else if (isPartArray(parameter)) { return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null); } else { return UNRESOLVABLE; } }
-
此时文件上传的请求参数封装就完毕了,后面就继续调用 doInvoke方法,进入目标方法调用逻辑继续往下执行
-
如果没有使用@RequestPart注解 会使用RequestParamMethodArgumentResolver来处理,底层还是调用了MultipartResolutionDelegate#resolveMultipartArugment
@Override @Nullable protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (servletRequest != null) { // 底层逻辑还是一直 Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } } Object arg = null; MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class); if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0) : files); } } if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; }