作为java开发,经常使用SpringBoot框架,那么掌握SpringBoot的请求的全流程还是十分必要的。
没有研究源码之前,有一些疑问:
@ResponseBody
,或者在类上加@RestController
@ResponseBody
,就是要去找视图解析器@ResponseBody
,那返回的json串,还是页面呢@ResponseBody
之后,还会经过视图解析器处理吗@RequestMapping("/index.html")
,且resoure目录下的static目录也有一个index.html页面会怎么样其实还有很多其他疑问,为什么会这样,其实很重要的一点就是,现在的开发推荐:约定大于配置,配置大于编码。
很多东西已经约定好的,直接告诉我们就这样子,按照这样写就对了,或者很多starter的使用,只要在yaml配置一下就可以了,对于编码的要求较少。那么因为框架或者工具等编码较少,我们就不知道这些框架本来的逻辑。有疑问就很自然了。
SpringBoot源码分析是十分必要的,包括但不限于
@SpringBootApplication
就是一个很重要的知识点new SpringApplication(primarySources).run(args);
,这里有2个很重要的操作,分别是new和run,分析里面的源码可以看到spring.factories文件里自动配置类加载,各种解析器,拦截器,bean的定义,加载和创建等操作。这次我就先分析第三个,看看一次请求经历了什么。
另外一篇文字是基本分析,有兴趣可以看看SpringMVC源码——doDispatch方法源码分析
下面以返回json格式为例
get请求入参
http://localhost:8080/spring/dataBinder?age=1&name=demoData
返回参数
{
“age”: 1,
“name”: “demoData”
}
@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;
}
分析开始点org.springframework.web.servlet.DispatcherServlet#doDispatch
该方法protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception
是请求的必经之路。因为DispatcherServlet本身就一个servlet,它配置的匹配映射地址是 / 。在springboot中它是默认配置好了,在springmvc中,我们可以在web.xml文件中看到它的配置如下:
<servlet>
<servlet-name>springMvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springMvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
由于DispatcherServlet是一个特殊的Servlet,一般Servlet 调用 service() 方法来处理客户端的请求,而DispatcherServlet在service()方法里,经过一些步骤之后,会调用doDispatch()方法。
因此我们从doDispatch开始入手分析。
下面以返回json格式为例。若分析返回其他类型,比如modelAndView或者静态文件等我们在后面再分析。
【始】
2020-09-30
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
,就是从箭头的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
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的。
恭喜你能看到第二个步骤结束,加油~~~
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
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
,该方法的主要作用就是执行真实方法逻辑,并返回modelAndView对象。
【续】2021-01-24
该操作是整个请求中最重要的一个步骤,里面从请求流程的角度,可以大概分为5个步骤:
RequestResponseBodyMethodProcessor
,而它支持的格式就是json。那么哪个HttpMessageConverter能解决json格式的呢,图中就有我们自己加入的fastjson,还有jackson。由于第一顺位是fastjson,所以就它处理返回值。[续-2021-01-25]
mappedHandler.applyPostHandle(processedRequest, response, mv);
就是执行拦截器的post方法的。这个非常简单,循环处理器执行链中的拦截器集合接口。
进入applyPostHandle
方法可以得到
代码中interceptor.postHandle(request, response, this.handler, mv);
就是执行拦截器的post方法。
同时也可以看到,若此时执行过程中抛出了异常,则在外层会catch住,并在第6步中特别处理异常的情况。
那么此时,读者可以考虑一下得出过滤器、拦截器、aop的执行顺序和位置了,这样就知道什么情况下使用它们了。
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写到页面上。
其实,该方法就是执行拦截器的after方法,正常情况下,在第6部里面就执行了。若出现异常了,则在外层执行。
到此,整个以json为例子的请求就结束了。通过源码分析,逻辑上并不复杂,只要了解大致流程即可了,对于细节,读者可以自己打断点,一步一步跟着流程走具体分析了。
那么,接下来还有视图或者资源的分析过程,
以后有时间继续分析。【待续】。。。。。最近有点忙