源码解读SpringMVC处理请求过程

写在前面

最近一直在往底层学习,所以并没有一直努力的更新帖子,趁今天有时间来更新一下把。

相信各位Java Coder,对Spring全家桶肯定不陌生, 甚至天天上班都是依靠这它,对于Spring中Web的框架肯定就是SpringMVC了,那么今天来着重讲解SpringMVC处理请求的流程。

当然,如果想把当前这篇文章看通透,建议可以先把SpringMVC的启动流程源码弄懂(不懂也没影响)。

笔者B站SpringMVC课程https://www.bilibili.com/video/BV1cL4y1j7ng?spm_id_from=333.999.0.0

源码案例

追源码的源头肯定都是一个简单的hello wolrd案例,这里展示出来只是为了帮助没有案例的同学是省去一些时间。

这里为了方便,笔者直接使用Maven+Spring boot来快速构建项目。



    4.0.0

    org.example
    SpringMVCSource
    1.0-SNAPSHOT

    
        8
        8
    

    
        spring-boot-starter-parent
        org.springframework.boot
        2.2.1.RELEASE
    

    

        
            org.springframework.boot
            spring-boot-starter-web
        

    

// controller层代码
@RestController
public class HelloController {

    @GetMapping("/hello1/{id}")
    public String hello1(
            @PathVariable("id")Integer id){
        return "hello world1";
    }

    @GetMapping("/hello2")
    public String hello2(@RequestParam("id") Integer id){
        return "hello world2";
    }
    
}

// 启动类
@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class,args);
    }
}

源码解读

写在前面(铺垫):

一个http的请求从客户端发到服务端。从TCP的建立和http协议的解析肯定是Tomcat帮你完成(这里不管是Tomcat+SpringMVC的项目,还是SpringBoot(内置Tomcat)的项目),都是Tomcat帮你完成了这些协议解析的事情(准备好数据),然后Tomcat初始化好Servlet以后经过他的每个容器的Pipline和一些过滤器最后来到了SpringMVC的DispatcherServlet中的doDispatch方法。

doDispatch方法中首先获取到当前请求对应的Controller层的详细信息(HandlerMethod)(这些数据在SpringMVC启动的时候就已经进入到MappingRegister中的缓存中)。然后获取到做事的HandlerAdapter,最终进入到HandlerAdapter中的handle()方法中去做详细的处理。

对以上的步骤做一个简单的总结如下:

  1. Tomcat建立连接,解析Http协议的数据。
  2. 初始化好Servlet,经过Tomcat的流水线和过滤器(为了做一些处理,比如404之类的)
  3. 最后来到SpringMVC的doDispatch方法中
  4. 根据当前请求获取到对应的Controller层的详细的数据
  5. 获取到当前处理请求的HandlerAdapter
  6. 并且执行HandlerAdapter的handle()方法来做详细的请求处理。

最终来到了ReuqestMappingHandlerAdapter中的handlerInternal方法中来做详细的处理

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

   // 创建一个ModelAndView,因为返回就是他
   ModelAndView mav;
    
   // 检查当前请求是否存在,不存在就直接抛出异常
   checkRequest(request);

   // 这边是看一个会话中是否需要上锁保证并发安全,这个synchronizeOnSession参数可以设置。
   if (this.synchronizeOnSession) {
      HttpSession session = request.getSession(false);
      if (session != null) {
         Object mutex = WebUtils.getSessionMutex(session);
         synchronized (mutex) {
            mav = invokeHandlerMethod(request, response, handlerMethod);
         }
      }
      else {
         // No HttpSession available -> no mutex necessary
         mav = invokeHandlerMethod(request, response, handlerMethod);
      }
   }
    
   // 没有设置参数就来到这边。
   else {
      // 真正处理请求的方法
      mav = invokeHandlerMethod(request, response, handlerMethod);
   }

   // 一些缓存的处理,这边具体没去了解
   if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
      if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
         applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
      }
      else {
         prepareResponse(response);
      }
   }

   return mav;
}

简单的做一个上面代码的总结:

  1. 判断当前请求是否存在
  2. 判断当前SpringMVC是否需要根据一个会话开启串行模式(参数可以自己设置)
  3. 不管是串行还是并发的处理最后还是走invokeHandlerMethod()方法去详细处理
  4. 根据响应头中的参数做判断,影响于缓存

所以看到invokeHandlerMethod()方法的具体逻辑

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   try {
      // 创建一个类型转换的工厂
      WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

      // 处理关于Model方面的内容
      ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

      // 真正做事的
      ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

      // argumentResolvers超级核心参数
      // 在handlerAdpater初始化的时候放入到缓存中的,也就是用来处理请求参数的多个核心类
      if (this.argumentResolvers != null) {
         invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
      }

      // 与上面类似,这边是用来处理返回值的。
      if (this.returnValueHandlers != null) {
         invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
      }
      invocableMethod.setDataBinderFactory(binderFactory);
      invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

      // 一个ModelAndView的容器,用来管理和存储Model的属性
      ModelAndViewContainer mavContainer = new ModelAndViewContainer();
      mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
      modelFactory.initModel(webRequest, mavContainer, invocableMethod);
      mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

      // 关于异步
      AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
      asyncWebRequest.setTimeout(this.asyncRequestTimeout);

      WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
      asyncManager.setTaskExecutor(this.taskExecutor);
      asyncManager.setAsyncWebRequest(asyncWebRequest);
      asyncManager.registerCallableInterceptors(this.callableInterceptors);
      asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
       
      
      if (asyncManager.hasConcurrentResult()) {
         Object result = asyncManager.getConcurrentResult();
         mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
         asyncManager.clearConcurrentResult();
         LogFormatUtils.traceDebug(logger, traceOn -> {
            String formatted = LogFormatUtils.formatValue(result, !traceOn);
            return "Resume with async result [" + formatted + "]";
         });
         invocableMethod = invocableMethod.wrapConcurrentResult(result);
      }

      // 处理请求的详细方法
      invocableMethod.invokeAndHandle(webRequest, mavContainer);
      if (asyncManager.isConcurrentHandlingStarted()) {
         return null;
      }

      // 使用ModelFactory工厂中的数据和ModelAndViewContainer容器中的数据创建ModelAndView
      return getModelAndView(mavContainer, modelFactory, webRequest);
   }
   finally {
      webRequest.requestCompleted();
   }
}

大概做个总结:

  1. 创建一个WebDataBinderFactory工厂,后面会使用这个来做类型转换的处理
  2. 创建ModelFactory,并且创建过程中会对全局注解@ControllerAdvice类中方法的标有@ModelAttribute的注解和当前HandleMethod(当前请求详情类)中标有@ModelAttribute并没有标有@RequestMapping的方法做一个率先处理(也就是在当前请求之前)
  3. 创建一个ModelAndViewContainer,目的是用来保存和管理当前请求出现的Model数据(容器)。
  4. 创建一个ServletInvocableHandlerMethod类,可以理解为处理请求的核心类,是处理请求的上下文对象,任何有关参数都会在其中。
  5. 关于请求异步的处理,因为在Serlvet3.0之后引入了异步请求的处理,但是笔者暂时没有去了解,不过万变不离其宗,也就是维护了一个内部的线程池,并且对于请求存在返回值,所以使用Callback。
  6. invokeAndHandle()方法是处理请求的详情
  7. 执行getModelAndView(),把之前ModelAndViewContainer对象中的数据获取到,最后构造ModeAndView对象为下面的请求做铺垫工作。


invokeAndHandle()方法的详情逻辑。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   // 处理的详情逻辑
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   
   // 响应的状态码
    setResponseStatus(webRequest);

   // 如果当前请求对应的Controller层接口没有返回值的一直处理
   if (returnValue == null) {
      if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
         disableContentCachingIfNecessary(webRequest);
         mavContainer.setRequestHandled(true);
         return;
      }
   }
   else if (StringUtils.hasText(getResponseStatusReason())) {
      mavContainer.setRequestHandled(true);
      return;
   }

   mavContainer.setRequestHandled(false);
   Assert.state(this.returnValueHandlers != null, "No return value handlers");
   try {

      // 对请求的返回值做一个详细处理,但是本帖暂时不讲
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      if (logger.isTraceEnabled()) {
         logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
   }
}

所以直接看到invokeForRequest()方法具体逻辑

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
      logger.trace("Arguments: " + Arrays.toString(args));
   }
   return doInvoke(args);
}

此时就是本帖想要讲的重点了,我们来一个简单的推理。

我们知道处理请求,其实也就是处理Controller层的接口,也就是处理Controller层的某个类中的某个方法。但是我们请求中可能有以下几种方式。

http://localhost:8080/hello2?id=1
http://localhost:8080/hello1/1
还有就是用PostMan或者前段发AJAX的Post请求,带请求体(对应Java也就是一个实体类Vo)

那么SpringMVC肯定是会帮我们在执行接口的目标方法之前来做请求参数和方法的参数做一个映射,所以我们接下来看SpringMVC是如何来做映射处理的。

所以直接看到getMethodArgumentValues方法

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   // 获取到HandlerMethod(一个HandlerMethod就是一个请求的相信信息,也就是一个对应的方法)中的方法参数
   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];
        
      // 初始化parameterNameDiscoverer。
      parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);

      // 因为我们当前providedArgs参数没有,所以这边没得参数
      args[i] = findProvidedArgument(parameter, providedArgs);
      if (args[i] != null) {
         continue;
      }

      // supportsParameter方法就开始根据MethodParameter的信息来遍历
      // SpringMVC中自带的20多个参数解析器,看一个参数解析器对应的上当前的方法中的参数
      // 如果没有对应上就直接抛出异常
      // 对应上了就会放入到缓存中。下面的方法会来获取
      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 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;
}

对以上代码做一个小总结(具体看注释):

  1. 从HandlerMethod中获取到当前方法所有的参数
  2. 对其做判空操作
  3. supportsParameter对所有的方法参数做遍历,并且再遍历HandlerMethodArgumentResolver来判断当前方法中的参数是否是SpringMVC所提供的的参数,如果不是就直接抛出异常,如果存在就加入到缓存。
  4. 执行resolveArgument方法来解析参数

看到resolveArgument具体的实现

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   // 获取到上面判断通过并加入到缓存中的SpringMVC的参数解析类
   HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
   if (resolver == null) {
      throw new IllegalArgumentException("Unsupported parameter type [" +
            parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
   }

   // 真正的解析的方法
   return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

这里只讲解几个常见的注解@PathVariable、@RequestParam,我们直接看到resolveArgument方法的实现。

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
   // 方法参数的名字
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    
   // 当前参数的封装对象
   MethodParameter nestedParameter = parameter.nestedIfOptional();

   // 获取到参数名字的具体对象,这个根据注解来的,如果是常见的注解,一般是字符串的类型
   Object resolvedName = resolveStringValue(namedValueInfo.name);
   if (resolvedName == null) {
      throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
   }

   // 具体的解析
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   if (arg == null) {
      if (namedValueInfo.defaultValue != null) {
         arg = resolveStringValue(namedValueInfo.defaultValue);
      }
      else if (namedValueInfo.required && !nestedParameter.isOptional()) {
         handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
      }
      arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
   }
   else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
      arg = resolveStringValue(namedValueInfo.defaultValue);
   }

   // 下面这些操作就是最早提到的WebDataBinderFactory工厂生产出WebDataBinder对象来做请求参数和方法参数之间的类型转换操作
   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
         arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
      }
      catch (ConversionNotSupportedException ex) {
         throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
               namedValueInfo.name, parameter, ex.getCause());
      }
      catch (TypeMismatchException ex) {
         throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
               namedValueInfo.name, parameter, ex.getCause());

      }
   }

   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

   return arg;
}

这里是AbstractNamedValueMethodArgumentResolver处理@PathVariable、@RequestParam之类的方法参数的父类,所以是肯定有模板方法来实现每个不同的注解解析。

源码解读SpringMVC处理请求过程_第1张图片

 RequestParamMethodArgumentResolver->@RequestParam注解的解析

@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 files = multipartRequest.getFiles(name);
      if (!files.isEmpty()) {
         arg = (files.size() == 1 ? files.get(0) : files);
      }
   }

    
   if (arg == null) {

      // @RequestParam中的参数是直接在请求参数中,所以直接拿前面获取到的方法参数名字来获取
      // 底层实现在Tomcat中的RequestFacade门面模式的request对象中,最后也就是在一个Map中做映射
      String[] paramValues = request.getParameterValues(name);
      if (paramValues != null) {
         arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
      }
   }
   return arg;
}

 因为@RequestParam注解是设置一个注解名字和Java类型和值,而请求参数中是直接通过?name=??? 的形式,所以直接获取到注解的名字做映射就可以获取到???的内容了。

PathVariableMethodArgumentResolver->@PathVarable注解的解析

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map uriTemplateVars = (Map) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

这边底层实现就不追了,最后也是到了Tomcat层面,Tomcat中也是维护了一个Map,但是这次的key是org.springframework.web.servlet.HandlerMapping.uriTemplateVariables,value是全部的@PathVarable注解的请求参数,但是这个value也是一个map,key是{}中的name,value是请求的参数(具体是在HandlerMapping中帮你处理的,并且假如到Tomcat中的request对象缓存中)。

所以这里把所有的@PathVarable获取到以后。

源码解读SpringMVC处理请求过程_第2张图片

最后解析完参数以后,再通过SpringMVC中WebDataBinder把请求参数和方法参数的类型转换以后,最后根据解析到的方法中的参数来到反射执行的方法。

源码解读SpringMVC处理请求过程_第3张图片

 

总结

口头描述,确实不管是笔者的描述能力还是读者的理解能力肯定都是存在偏差,所以笔者目前也是经常在B站直播,大家可以关注。

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

你可能感兴趣的:(Spring,MVC系列,源码解读,spring,SpringMVC,spring,boot,java,后端)