开发者

SpringBoot文件上传的原理解析

目录
  • 一、请求进入,使用文件上传解析器判断并封装
    • 文件上传解析器:只能接收标准的 Servlet 方式上传的文件
    • DispatcherServlet.class —— doDispatch()方法
  • 二、参数解析器解析请求中的文件内容封装成 MultipartFile
    • 三、将 request 文件封装为一个 Map (Map<String, MultipartFile>)

      一、请求进入,使用文件上传解析器判断并封装

      文件上传解析器:只能接收标准的 Servlet 方式上传的文件

      @ConditionalOnMissingBean({MultipartResolver.class}) 判断容器中无文件上传解析器,若无自动创建

          @ConditionalOnMissingBean({MultipartResolver.class}) // 判断注解
          public StandardServletMultipartResolver multipartResolver() {
              StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
              multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
              return multipartResolver;
          }

      DispatcherServlet.class —— doDispatch()方法

      与文件上传相关功能有关的语句,请见下面的注释:

          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.getHandlehttp://www.devze.comr());
                          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(processedRequestphp, 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");
                 www.devze.com     }
                  } 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);
          }

      二、参数解析器解析请求中的文件内容封装成 MultipartFile

      文件请求参数解析器:

      SpringBoot文件上传的原理解析

      (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, naandroidme);
                      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.getMejsthodArgumentValues(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;
                          }
                      }
                  }

      三、将 request 文件封装为一个 Map (Map<String, MultipartFile>)

      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<String, MultipartFile>)

      通过观察源码,可以得到许多 SpringBoot 为我们封装好的文件工具类,如 FileCopyUtils 实现文件流的拷贝

      到此这篇关于SpringBoot文件上传的原理解析的文章就介绍到这了,更多相关SpringBoot文件上传内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜