SpringBoot源码——请求全过程源码分析——一步一步详细分析


前言

作为java开发,经常使用SpringBoot框架,那么掌握SpringBoot的请求的全流程还是十分必要的。

没有研究源码之前,有一些疑问:

  • 为什么要返回json串的时候需要在方法上加@ResponseBody,或者在类上加@RestController
  • 为什么返回String类型,不加@ResponseBody,就是要去找视图解析器
  • 返回modelAndVIew类型并加@ResponseBody,那返回的json串,还是页面呢
  • 视图解析器可以有多个吗,如何选择的?加了@ResponseBody之后,还会经过视图解析器处理吗
  • 为什么有时候使用了fastjson,最好也需要加fastjson的消息处理器HttpMessageConverters
  • 若加上方法上加@RequestMapping("/index.html"),且resoure目录下的static目录也有一个index.html页面会怎么样

其实还有很多其他疑问,为什么会这样,其实很重要的一点就是,现在的开发推荐:约定大于配置,配置大于编码。

很多东西已经约定好的,直接告诉我们就这样子,按照这样写就对了,或者很多starter的使用,只要在yaml配置一下就可以了,对于编码的要求较少。那么因为框架或者工具等编码较少,我们就不知道这些框架本来的逻辑。有疑问就很自然了。


一、SpringBoot源码

SpringBoot源码分析是十分必要的,包括但不限于

  • 包括启动类上的注解@SpringBootApplication就是一个很重要的知识点
  • 另外main方法的运行之后,源码里new SpringApplication(primarySources).run(args); ,这里有2个很重要的操作,分别是new和run,分析里面的源码可以看到spring.factories文件里自动配置类加载,各种解析器,拦截器,bean的定义,加载和创建等操作。
  • 还有就是一个前端的get/post请求到controller方法经历了什么。

这次我就先分析第三个,看看一次请求经历了什么。
另外一篇文字是基本分析,有兴趣可以看看SpringMVC源码——doDispatch方法源码分析

二、代码准备

1、请求

下面以返回json格式为例

get请求入参

http://localhost:8080/spring/dataBinder?age=1&name=demoData

返回参数
{
“age”: 1,
“name”: “demoData”
}

2、controller层

@Controller
@RequestMapping("/spring")
@Slf4j
public class SpringController {
    @GetMapping("/dataBinder")
    @ApiOperation(value = "我是一个可爱的测试获取dataBinder的接口")
    @ResponseBody
    public DataBinder dataBinder(DataBinder dataBinder) {
        return dataBinder;
    }
}
@Data
class DataBinder {
    String name;
    Integer age;
}

3、分析起点

分析开始点org.springframework.web.servlet.DispatcherServlet#doDispatch
该方法protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception是请求的必经之路。因为DispatcherServlet本身就一个servlet,它配置的匹配映射地址是 / 。在springboot中它是默认配置好了,在springmvc中,我们可以在web.xml文件中看到它的配置如下:

<servlet>  
        <!-- 配置DispatcherServlet -->  
      <servlet-name>springMvc</servlet-name>  
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
      <!-- 指定spring mvc配置文件位置 不指定使用默认情况 -->  
      <init-param>     
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring/spring-mvc.xml</param-value>
      </init-param>  
      <!-- 设置启动顺序 -->  
      <load-on-startup>1</load-on-startup>  
  </servlet>

  <!-- ServLet 匹配映射 -->
  <servlet-mapping>
      <servlet-name>springMvc</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>

由于DispatcherServlet是一个特殊的Servlet,一般Servlet 调用 service() 方法来处理客户端的请求,而DispatcherServlet在service()方法里,经过一些步骤之后,会调用doDispatch()方法。
因此我们从doDispatch开始入手分析。
在这里插入图片描述

三、源码分析

下面以返回json格式为例。若分析返回其他类型,比如modelAndView或者静态文件等我们在后面再分析。

【始】
2020-09-30

1.getHandler()方法

mappedHandler = getHandler(processedRequest);这里是获取HandlerExecutionChain处理器执行链,里面包含处代表具体处理逻辑的方法HandlerMethod对象和若干个拦截器集合(包括spring自带的和自己手动添加的)。

进入getHandler方法org.springframework.web.servlet.DispatcherServlet#getHandler

我们可以看到this.handlerMappings就是执行路径映射的一个处理器,这里有5个,它可以根据请求的路径/spring/dataBinder得到真正的处理者HandlerMethod对象。

比如第一个是requestMappingHandlerMapping,它可以根据controller类的方法路径进行映射。

还有最后一个SimpleUrlHandlerMapping,就是处理路径到resource目录里面静态文件的。比如访问为localhost:8080/index.html,访问一个页面。
在这里插入图片描述
目前我们进入第一个HandlerMapping的getHandler()方法org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

可以看到第一句Object handler = getHandlerInternal(request);,这个就是获取一个标识真实的处理方法的对象HandlerMethod。
在这里插入图片描述
进入该方法,继续跟代码会到org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getHandlerInternal

里面有一个super.getHandlerInternal(request);

继续跟会到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

如下图所示:
在这里插入图片描述
第一行String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);获取请求路径名为/spring/dataBinder。

红色框框里面的HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);就是进一步去获取真正的处理方法HandlerMethod

继续进入该方法lookupHandlerMethod,org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

截图如下
在这里插入图片描述

第2行List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);,就是从箭头的12个路径中找有没有与请求路径/spring/dataBinder一致的,可以看到第8个就是一致的。那么现在就获取到了一个该路径的一个list,list也只有一个元素,因为只有第8个匹配到了。

在经过addMatchingMappings(directPathMatches, matches, request);,该方法就是去找正在处理的方法,找到之后就能获取到matches的list,里面也是一个元素,Match也就是包含了handlerMethod的一个类而已。

然后该方法最后几行,根据bestMatch.handlerMethod;就得到真正的处理方法。
在这里插入图片描述
现在已经获取到了handlerMethod,那就开始返回

返回到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

里面有return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);即不为空,那就执行一个handlerMethod.createWithResolvedBean(),这个方法就相当于new了一下handlerMethod,返回这个新的handlerMethod。
在这里插入图片描述
然后再一直返回到org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
在这里插入图片描述
到这里第一行就完成了,Object handler就是com.jd.ins.qsm.demo.web.controller.SpringController#dataBinder(DataBinder),真正的方法处理器。

接下里该方法中间断点处的代码HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);这个是获取HandlerExecutionChain执行链的方法。

截图如下:
在这里插入图片描述

第一句,就是问Object (handlerMethod)这个是不是HandlerExecutionChain执行链这个类型,属于就转换成执行链,显然不是,那就new一个,并设置handlerMethod属性。

接下里一个for循环,获取的所有的拦截器,然后加入到方法执行链中,包括我自己自定义的testInterceptor拦截器。然后返回处理器执行链。
一直返回到org.springframework.web.servlet.DispatcherServlet#getHandler
在这里插入图片描述
由于第一个requestMappingHandlerMapping已经能够处理,并返回了不为空的处理器执行链,那这里也就直接返回该执行链到最开始的方法doDispatch()方法的步骤
mappedHandler = getHandler(processedRequest);
在这里插入图片描述
此时这个执行链就包含了一个真正的处理方法HandlerMethod,还有3个拦截器,其中一个是我自定义的一个TestInterceptor。

至此,第一步获取处理器执行链结束。

第一个步骤,就是获取HandlerExecutionChain处理器执行链。
通过多个handlerMapping处理器映射器去寻找真正能够处理请求路径的地方,比如第一个handlerMapping就是requestMappingHandlerMapping,通过它可以寻找到标注了@requestMapping等注解的方法,并根据请求路径看有没有与之对应的方法路径,若正好有,那就返回一个HandlerMethod。同时这个执行链还会包含多个拦截器(若有)。**


恭喜你能看到第一个步骤结束,其实你会发现不是很难,加油~~~



【续】
2020-10-17

2.getHandlerAdapter()方法

getHandlerAdapter(mappedHandler.getHandler());就是获取真正的处理方法handlerMethod的一个适配器的类HandlerAdapter。它对真正方法的执行做了很多的支持工作。

进入org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
在这里插入图片描述
我们看到this.handlerAdapters有4个适配器,其中第一个就是requestMappingHandlerAdapter,然后看它支不支持我们的真正的处理方法handlerMethod。

下一步进入org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#supports
在这里插入图片描述
很显然,我们的handler本来就是handlerMethod,所以肯定返回true。

也就是这个requestMappingHandlerAdapter这个适配器能够支持handlerMethod,那么就返回了4个adapter中的第一个requestMappingHandlerAdapter。
在这里插入图片描述

从上面的截图中,可以看到这个适配器requestMappingHandlerAdapter还是有很多属性的。这些属性对真正处理方法执行的过程的之前或者之后做了一些工作。

其中里面蓝色框框的属性returnValueHandler和messagConverters比较重要,后期我们会讲到这个2个的作用。

至此,第二步获取处理器适配器结束。

第二个步骤,就是获取处理器适配器HandlerAdapter。
通过多个HandlerAdapter看它们能否支持真正处理方法,比如第一个handlerMapping就是requestMappingHandlerAdapter,它就刚好能支持 处理器方法类型 为HandlerMethod的。

恭喜你能看到第二个步骤结束,加油~~~

3.applyPreHandle()方法

mappedHandler.applyPreHandle(processedRequest, response),执行执行链中的拦截器的pre方法,这个很好理解,按照拦截器的先后顺序先后执行pre方法。

进入org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
在这里插入图片描述
可以看到,有三个拦截器,其中第一个就是我们自定义的,继续执行到我们自定义的拦截器pre方法中,
在这里插入图片描述
可以看到,我们里面就打印了一句话而已,并且返回true。
返回true之后,这是这个方法interceptor.preHandle(request, response, this.handler)就是true,加入!,那就是false,也就不会执行if里面的逻辑。

接下来就都依次的执行其他拦截器的pre方法。

若有某一拦截器个pre方法返回了false,那么这里取反之后就是true,那就会执行if里面的方法,那样也会执行拦截器的triggerAfterCompletion方法,这个方法的工作可以是处理一些收尾工作。并返回false到最上层方法。
在这里插入图片描述
最上面的这一层,也是取反判断。

若一切顺利,即所有的pre方法都返回的true,则不会执行上面截图中if里面的return,所以若哪个拦截器pre方法返回了false,那这里也就执行了return,整个请求过程到这里就提前GameOver了。

从这里可以看出,拦截器pre方法可以进行鉴权,日志等操作。

第三步,执行拦截器的pre方法。
若所有的pre方法都返回true,则请求会继续,若有一个返回了false,则整个请求结束

恭喜你能看到第三个步骤结束,加油~~~

【续】
2020-10-19

4.ha.handle()方法

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());,该方法的主要作用就是执行真实方法逻辑,并返回modelAndView对象。

【续】2021-01-24
该操作是整个请求中最重要的一个步骤,里面从请求流程的角度,可以大概分为5个步骤:

  • 处理入参,主要根据databinder处理,格式转换,赋值,和校验Validated。
  • aop,即处理方法的前置,以及【处理完业务方法之后】后置,返回和异常通知。
  • 正在处理逻辑方法
  • 处理出参类型,即判断反参的类型,比如是否为String,是否为modelAndView,是否加了@ResponseBody、@RestController等。根据HandlerMethodReturnValueHandler,里面有十几种,按照顺序,排名前三的就是处理,ModelAndView,Model,View的,之后还有处理@ResponseBody、@RestController的,之后还有处理String的。
    在这里插入图片描述
  • 处理出参返回的格式,比如application/json等。根据第三步可以知道,处理返回值的是使用了RequestResponseBodyMethodProcessor,而它支持的格式就是json。那么哪个HttpMessageConverter能解决json格式的呢,图中就有我们自己加入的fastjson,还有jackson。由于第一顺位是fastjson,所以就它处理返回值。
    在这里插入图片描述
    由于本次返回的json,所以并不经过视图。所以是没有ModelAndView对象,即为null,
    具体每步代码分析,以后截图分析,读者现在需要上面大概5步骤即可。

[续-2021-01-25]

5.mappedHandler.applyPostHandle()方法

mappedHandler.applyPostHandle(processedRequest, response, mv);就是执行拦截器的post方法的。这个非常简单,循环处理器执行链中的拦截器集合接口。
进入applyPostHandle方法可以得到
在这里插入图片描述
代码中interceptor.postHandle(request, response, this.handler, mv);就是执行拦截器的post方法。
同时也可以看到,若此时执行过程中抛出了异常,则在外层会catch住,并在第6步中特别处理异常的情况。
那么此时,读者可以考虑一下得出过滤器、拦截器、aop的执行顺序和位置了,这样就知道什么情况下使用它们了。

6.processDispatchResult()方法

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);是处理视图的方法。
在这里插入图片描述
进入之后,需要注意图中的三个红色地方。
第一个,是不仅仅处理第5步中的异常的的,实际上它是处理上面所有步骤的所产生的异常,根据外层的catch的作用范围即可知道。
这么会有一些异常解析器来处理异常。

第二个,就是render方法。里面就是根据view名字找到对应的视图解析器,并将逻辑视图render渲染为物理视图。

第三个,就是拦截器的after收尾方法,也是按序执行。

针对,第二个的render,由于本次请求是不经过视图的,所以大概描述一下
进入render方法
在这里插入图片描述
第一处,就是拿到视图名。
第二处,就是拿到逻辑视图View
在这里插入图片描述
可以看到视图解析器有5个,其中就有我们非常熟悉的的thymeleaf和internal(jsp),他们按照list顺序,根据视图名,判断是否是自己的管辖的。然后得到合适的视图解析器。
第三处,就是得到逻辑视图之后,又调用一个render方法,不同的视图解析器有各自的实现,通用逻辑就是将数据和页面结合,然后response.getWriter().flush();通过response写到页面上。

7.triggerAfterCompletion()方法

其实,该方法就是执行拦截器的after方法,正常情况下,在第6部里面就执行了。若出现异常了,则在外层执行。
在这里插入图片描述

到此,整个以json为例子的请求就结束了。通过源码分析,逻辑上并不复杂,只要了解大致流程即可了,对于细节,读者可以自己打断点,一步一步跟着流程走具体分析了。


那么,接下来还有视图或者资源的分析过程,

以后有时间继续分析。【待续】。。。。。最近有点忙

总结

评论 1 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

可乐多点冰

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值