自动配置

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
  3. @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
  4. @ConditionalOnWebApplication(type = Type.SERVLET)
  5. @EnableConfigurationProperties(MultipartProperties.class)
  6. public class MultipartAutoConfiguration {
  7. private final MultipartProperties multipartProperties;
  8. public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
  9. this.multipartProperties = multipartProperties;
  10. }
  11. @Bean
  12. @ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
  13. public MultipartConfigElement multipartConfigElement() {
  14. return this.multipartProperties.createMultipartConfig();
  15. }
  16. @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
  17. @ConditionalOnMissingBean(MultipartResolver.class)
  18. public StandardServletMultipartResolver multipartResolver() {
  19. StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
  20. multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
  21. return multipartResolver;
  22. }
  23. }

MultipartConfigElement

看看谁用到了这个类
居然是StandardServletMultipartResolver的注释说到了它.


Standard implementation of the MultipartResolver interface, based on the Servlet 3.0 Part API. To be added as “multipartResolver” bean to a Spring DispatcherServlet context, without any extra configuration at the bean level (see below).
Note: In order to use Servlet 3.0 based multipart parsing, you need to

  1. mark the affected servlet with a “multipart-config” section in web.xml,
  2. or with a javax.servlet.MultipartConfigElement in programmatic servlet registration,
  3. or (in case of a custom servlet class) possibly with a javax.servlet.annotation.MultipartConfig annotation on your servlet class.

Configuration settings such as maximum sizes or storage locations need to be applied at that servlet registration level; Servlet 3.0 does not allow for them to be set at the MultipartResolver level
相当于向servlet注册一个上传的配置,做法就是multipartProperties->MultipartConfigElement,属性映射


翻译过来就是

  1. 可以通过xml或者注解->注册servlet
  2. 或者通过配置属性,让springmvc的dispatchServlet具备这个上传配置

    StandardServletMultipartResolver

    注释就在上面

    代码流程

    1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    2. HttpServletRequest processedRequest = request;
    3. HandlerExecutionChain mappedHandler = null;
    4. boolean multipartRequestParsed = false;
    5. try {
    6. ModelAndView mv = null;
    7. Exception dispatchException = null;
    8. try {
    9. //逻辑this.multipartResolver.resolveMultipart(request)->new StandardMultipartHttpServletRequest(request, this.resolveLazily)
    10. //具体看下面代码,不是懒加载的话就直接解析文件流
    11. processedRequest = checkMultipart(request);
    12. //true
    13. multipartRequestParsed = (processedRequest != request);
    14. // Determine handler for the current request.
    15. mappedHandler = getHandler(processedRequest);
    16. if (mappedHandler == null) {
    17. noHandlerFound(processedRequest, response);
    18. return;
    19. }
    20. // Determine handler adapter for the current request.
    21. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    22. // Process last-modified header, if supported by the handler.
    23. String method = request.getMethod();
    24. boolean isGet = "GET".equals(method);
    25. if (isGet || "HEAD".equals(method)) {
    26. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    27. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    28. return;
    29. }
    30. }
    31. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    32. return;
    33. }
    34. // Actually invoke the handler.真正执行的逻辑,包括参数的确定和返回值计算,关键在于参数确定,见下面
    35. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    36. if (asyncManager.isConcurrentHandlingStarted()) {
    37. return;
    38. }
    39. applyDefaultViewName(processedRequest, mv);
    40. mappedHandler.applyPostHandle(processedRequest, response, mv);
    41. }
    42. catch (Exception ex) {
    43. dispatchException = ex;
    44. }
    45. catch (Throwable err) {
    46. // As of 4.3, we're processing Errors thrown from handler methods as well,
    47. // making them available for @ExceptionHandler methods and other scenarios.
    48. dispatchException = new NestedServletException("Handler dispatch failed", err);
    49. }
    50. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    51. }

    解析文件流

    ```java public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)

    1. throws MultipartException {

    @Nullable private MultiValueMap multipartFiles;

    //构造器 根据懒加载配置,看看是否进行解析 public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)

    1. throws MultipartException {
    2. super(request);
    3. if (!lazyParsing) {
    4. parseRequest(request);
    5. }

    } //解析逻辑 private void parseRequest(HttpServletRequest request) {

    1. try {
    2. //servlet3.0原生api,直接从request中获取文件流,封装为Part
    3. Collection<Part> parts = request.getParts();
    4. this.multipartParameterNames = new LinkedHashSet<>(parts.size());
    5. //封装到单key多value的Map中去
    6. MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
    7. for (Part part : parts) {
    8. String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
    9. ContentDisposition disposition = ContentDisposition.parse(headerValue);
    10. String filename = disposition.getFilename();
    11. if (filename != null) {
    12. if (filename.startsWith("=?") && filename.endsWith("?=")) {
    13. filename = MimeDelegate.decode(filename);
    14. }
    15. files.add(part.getName(), new StandardMultipartFile(part, filename));
    16. }
    17. else {
    18. this.multipartParameterNames.add(part.getName());
    19. }
    20. }
    21. //最终封装的放在这个request中的multipartFiles属性
    22. setMultipartFiles(files);
    23. }
    24. catch (Throwable ex) {
    25. handleParseFailure(ex);
    26. }

    } /**

    • Obtain the MultipartFile Map for retrieval,
    • lazily initializing it if necessary.
    • @see #initializeMultipart() */ protected MultiValueMap getMultipartFiles() { //根据属性,如果没有就加载 //懒加载true:则初始化的时候没有准备这个值,那么就是空的,就要初始化 //懒加载false:已经初始化好了这个属性,直接就可以返回这个属性 if (this.multipartFiles == null) {
      1. //真正逻辑在parseRequest(getRequest()),跟构造器那个同样的方法
      2. initializeMultipart();
      } return this.multipartFiles; }

}

  1. <a name="C0E0h"></a>
  2. ## 确定参数
  3. RequestMappingHandlerAdapter<br />#方法调用栈<br />handle<br />handleInternal<br />invokeHandlerMethod<br />new ServletInvocableHandlerMethod()-->设置参数-->invocableMethod.invokeAndHandle**(**webRequest, mavContainer**)**;<br />ServletInvocableHandlerMethod<br />#方法调用栈<br />invokeAndHandle<br />invokeForRequest<br />getMethodArgumentValues<br />策略模式的应用
  4. ```java
  5. public class InvocableHandlerMethod extends HandlerMethod
  6. private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
  7. protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
  8. Object... providedArgs) throws Exception {
  9. MethodParameter[] parameters = getMethodParameters();
  10. if (ObjectUtils.isEmpty(parameters)) {
  11. return EMPTY_ARGS;
  12. }
  13. Object[] args = new Object[parameters.length];
  14. for (int i = 0; i < parameters.length; i++) {
  15. MethodParameter parameter = parameters[i];
  16. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  17. args[i] = findProvidedArgument(parameter, providedArgs);
  18. if (args[i] != null) {
  19. continue;
  20. }
  21. //核心代码1:RequestPartMethodArgumentResolver#supportsParameter->getArgumentResolver(1遍历 2缓存 3break)
  22. //逻辑:parameter.hasParameterAnnotation(RequestPart.class)->true
  23. if (!this.resolvers.supportsParameter(parameter)) {
  24. throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
  25. }
  26. try {
  27. //核心代码2:RequestPartMethodArgumentResolver#resolveArgument->getArgumentResolver(1遍历 2缓存 3break)resolver.resolveArgument
  28. //resolver.resolveArgument逻辑要看代码
  29. args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
  30. }
  31. catch (Exception ex) {
  32. // Leave stack trace for later, exception may actually be resolved and handled...
  33. if (logger.isDebugEnabled()) {
  34. String exMsg = ex.getMessage();
  35. if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
  36. logger.debug(formatArgumentError(parameter, exMsg));
  37. }
  38. }
  39. throw ex;
  40. }
  41. }
  42. return args;
  43. }

上传文件请求参数解析器RequestPartMethodArgumentResolver

  1. public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
  2. public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
  3. NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  4. HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
  5. Assert.state(servletRequest != null, "No HttpServletRequest");
  6. RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
  7. boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
  8. String name = getPartName(parameter, requestPart);
  9. parameter = parameter.nestedIfOptional();
  10. Object arg = null;
  11. //逻辑代码
  12. Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
  13. if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
  14. arg = mpArg;
  15. }
  16. return adaptArgumentIfNecessary(arg, parameter);
  17. }

干活的人MultipartResolutionDelegate,文件流已经封装在request里面的,何时封装的呢?就在上面的解析文件流

  1. public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
  2. throws Exception {
  3. MultipartHttpServletRequest multipartRequest =
  4. WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
  5. boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
  6. //单文件
  7. if (MultipartFile.class == parameter.getNestedParameterType()) {
  8. if (!isMultipart) {
  9. return null;
  10. }
  11. if (multipartRequest == null) {
  12. multipartRequest = new StandardMultipartHttpServletRequest(request);
  13. }
  14. return multipartRequest.getFile(name);
  15. }
  16. //文件集合Collection
  17. else if (isMultipartFileCollection(parameter)) {
  18. if (!isMultipart) {
  19. return null;
  20. }
  21. if (multipartRequest == null) {
  22. multipartRequest = new StandardMultipartHttpServletRequest(request);
  23. }
  24. List<MultipartFile> files = multipartRequest.getFiles(name);
  25. return (!files.isEmpty() ? files : null);
  26. }
  27. //文件数组
  28. else if (isMultipartFileArray(parameter)) {
  29. if (!isMultipart) {
  30. return null;
  31. }
  32. if (multipartRequest == null) {
  33. multipartRequest = new StandardMultipartHttpServletRequest(request);
  34. }
  35. List<MultipartFile> files = multipartRequest.getFiles(name);
  36. return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
  37. }
  38. .......
  39. else {
  40. return UNRESOLVABLE;
  41. }
  42. }

总结

springmvc在servlet3.0上做的封装不多
multipartRequest.getFile
multipartRequest.getFiles(name)
解析的文件流存放位置:
image.png
用的时候根据MultipartFile接口的api去使用即可,
根据参数名,操作不同的流,或者是同名参数的多个流操作
设计还是不错的