Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么


(友情提示: 代码及注释内容比较多,为了不影响体验,建议大屏观看哦)

请求它悄悄的来了,正如它悄悄地走~


页面的发起一个请求,你只需要使用@Controller,@RequestParam,@RequestBody等待一系列注解,就可以把对应的请求打到你的方法上,并且参数都已经给你封装好了,这一切发生的过程,就像梦一样。而这里,就是让你了解梦的地方!

Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

request.png

以上是最简化之后的请求处理的流程图,浏览器发起请求,Spring接收到请求,通过一系列请求前处理,然后到达我们的业务controller,当我们业务代码处理完之后,又到了一系列请求后处理,后处理完成之后,再把数据返回到浏览器。

今天的重点,就是捋通一下这整个流程的来龙去脉!首先我们需要知道请求的入口,Spring是基于servlet的,那么,DispatcherServlet就是我们入手的入口了,然后顺藤摸瓜!

DispatchrServlet

我们先来看哈DispathcerServlet的继承关系。

Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

dispacherServlet_class.png

springmvc是基于servlet来的,还记的你配置web.xml的时候吗,你只需要配置一个DispatcherServlet,然后所有请求处理都要经过的这一个servlet,这个servlet实际上只是做请求的分发处理,找到合适的处理器,处理,并返回结果,这个是springmvc主要的实现方式,现在我们来看这个核心DispatcherServlet的处理流程。

这里先简单说一下入口,servlet处理是从service(request,response)方法开始的,这个方法的实现在Httpservlet中。这个方法最终会根据请求的method(post,get等)转到子类FrameServlet中的doPost(),doGet()等方法进行处理,在这些方法中,最终又会委托给processRequest(request, response)方法进行处理,这里就直接从这个方法入手了,前面的啥service(),doPost()等的就自己去瞧了。

processRequest(request, response)

//FrameworkServlet.Java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//为了保证当前线程的LocalContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性(需要点进去看,buildLocaleContext()方法中)
//根据当前request创建对应的LocaleContext和requestAttibutes,并绑定到当前线程
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
...
try {
//委托给doSerivce()做进一步处理
doService(request, response);
}
...
finally {
//请求结束后恢复线程原始状态
resetContextHolders(request, previousLocaleContext, previousAttributes);
//请求处理结束后无论成功与否,发布事件通知
...
publishRequestHandledEvent(request, startTime, failureCause);
}
}

从FrameworkServlet的processRequest()方法中,我们可以看到以下流程(都在注释中):

  1. 为了保证当前线程的LocalContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性;
  2. 根据当前request创建对应的LocaleContext和requestAttibutes,并绑定到当前线程;
  3. 委托给doSerivce()做进一步处理;
  4. 请求结束后恢复线程原始状态;
  5. 请求处理结束后无论成功与否,发布事件通知。

doService(request, response)

这个时候,我们来到了DispacherServlet#doService()方法。

//DispacherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
//请求分发处理
doDispatch(request, response);
}
...
}

根据spring一贯的风格,这个时候的doservice依然是在做一些准备工作,准备工作中spring会把我们初始化的各种resolver和WebapplicationContext放到request的Attribute中,以便之后使用。重点来到doDispatch(request, response);

doDispatch

//DispacherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
HandlerExecutionChain mappedHandler = null;
//如果是文件上传使用的MultipartContent类型的request,则将request转换成MultipartHtptServletRequest类型的request
processedRequest = checkMultipart(request);
...
// 根据request信息寻找对应的handler,
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//如果没有找到对应的handler,则通过response返回错误信息
noHandlerFound(processedRequest, response);
return;
}
// 通过找到的handler,寻找它的适配器handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
// 调用拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// 真正的激活handler调用,并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
...
//视图名称的转换,用于需要添加前后缀的情况
applyDefaultViewName(request, mv);
// 调用拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
...
//处理结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
}

getHandler

gethandler最终会调用到如下方法,可以看到其实就是遍历我们初始化的所有handlerMappings来寻找一个合适的handler并返回。

// DispacherServlet.java
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
...
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

以我们最熟悉的@Controller注解方式来分析,系统会注册默认的RequestMappingHandlerMapping,我们进入hm.getHandler(request)查看,这个方法来自于超类AbstractHandlerMapping

//AbstractHandlerMapping.java
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据request获取对应的handler,由子类AbstractUrlHandlerMapping实现
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果没有handler,则使用默认的handler
handler = getDefaultHandler();
}
//如果也没有默认的handler,则不继续处理,返回null
if (handler == null) {
return null;
}
//如果handler是一个string类型的,那说明这是个bean的name,根据它去获取对应的bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//加入拦截器,并返回添加了拦截器的handler处理链
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
...
return executionChain;
}
//AbstractUrlHandlerMethodMapping.java
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//查找对应的handler
HandlerMethod handler = lookupHandlerMethod(lookupPath, request);
...
return handler;
}

protected HandlerMethod lookupHandlerMethod(String urlPath, HttpServletRequest request) throws Exception {
// 这个地方就不贴代码了
// 这里面的逻辑就是从注册到mappingRegistry中的映射中,找到一个最匹配的handler
...
}

有图真相,来看这个HandlerMethod.

Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

handler_method.png

继续来到后面的getHandlerExecutionChain

// AbstractHandlerMapping.java
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 强转或者创建一个chain对象
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 这个lookupPath就是我们的请求路径(servletPath)
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
// 查找可以应用于当前路径的拦截器,加入执行连
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
// 返回对象
return chain;
}

整理流程比较简单,注释也很清楚,就不过多缀述了。我们通过调试来看一下这个生成的chain.

Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

controller_handler_chain.png

至此,我们DispatcherServlet#getHandler()就执行完成了,拿到了执行链chain,剩下的就是执行了。但是我们的接口方法需要怎样调用执行呢?这个时候就到适配器HandlerAdapter登场了,每一种类型的Controller接口,都会有对应的执行适配器,其他类型的Controller我就不细讲了,等咱看完这个最具代表性的之后,其他的都是小菜一碟,都将被你手到拈来!

getHandlerAdapter

getHandlerAdapter也是遍历之前注册的所有adapter来获取对应的handler的adapter,注意入参,是handler,就是我们的controller。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
...
if (ha.supports(handler)) {
return ha;
}
}
...
}

直接上图,可以看到handlerAdapters有好几个,哈哈,还有个我们上次自定义的那个Adapter,我们当前的接口使用的是@Controller注解的方式,所以我们直接来看RequestMappingHandlerAdapter

Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

handler_adapter_1.png
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

由于这个方法我们上次已经分析过来,所以就不解释了,逻辑也很简单,拿到了适配器,也拿到了执行链,所谓万事俱备只欠东风,剩下的就是调用了。

拦截器的前置后置调用在之前的DispatcherrServlet#doDispatch()方法中也注释的比较清楚了,而且逻辑也很简单,所以就直接略过了,我们直接来看调用我们接口方法的这一段。

ha.handle()

这个ha是上一步获取到的handlerAdapter,不是第一步获取的的handler哦,这里才真正开始执行调用我们的controller逻辑。

//AbstractHandlerMethodAdapter.java
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
//一贯风格又来了,这个由子类实现
return handleInternal(request, response, (HandlerMethod) handler);
}
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod)
throws Exception
{
ModelAndView mav;
// 其他都是扯淡,直接看重点
mav = invokeHandlerMethod(request, response, handlerMethod);
return mav;
}

又是Spring的一贯的俄罗斯套娃了,继续跟。这个方法有点长稍微有点长~

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod)
throws Exception
{
// 封装一个ServletWebRequest
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 创建参数绑定器
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

// 把我们的handler封装成更强大的调用对象,因为其中还涉及参数的绑定等逻辑,所以仅仅是HandlerMethod对象还不够
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
// 设置方法参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
// 设置返回值处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 设置参数绑定器
invocableMethod.setDataBinderFactory(binderFactory);
// 设置参数参数名称解析器
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 这里省略一部分其他不太关注的逻辑
// ...
// 开始调用我们的目标接口方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);

// 根据返回值的类型,来处理最终的mav,比如视图渲染
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}

由于代码比较长,所以逻辑都在代码里注释了,比较清晰,接下来继续跟进invocableMethod#invokeAndHandle()这个目标方法。

invocableMethod.invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 调用我们的接口方法,拿到返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 这部分是不重要的逻辑,主要是如果接口返回的是视图的话,就会在这部分逻辑中终止掉
//... 处理是视图的情况
try {
// 如果不是视图,返回值丢给返回值处理器器处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}

可以看到我们的controller最终被调用了。调用的过程是在invokeForRequest()方法中发生的,我们来继续看一下。

invokeForRequest

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 封装请求参数,这部分就是做我们请求参数到接口方法上的封装,重点***
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
...
// 执行调用
return this.doInvoke(args);
}

protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(this.getBridgedMethod());
try {
// 反射调用
return this.getBridgedMethod().invoke(this.getBean(), args);
}
...
}

可以看到,在当前的方法中,第一步进行了请求参数的封装,比如对注解@requestParam,@RequestBody等的处理,拿到封装的参数后,执行了对controller 的调用。

然后我们接着,然后我们后退一下,回到方法invocableMethod#invokeAndHandle(),spring在拿到调用返回结果后,通过returnValueHandlers.handleReturnValue()方法对结果的返回值做后续的处理。比如说@ResponseBody等注解。

这里有两个地方,一个是请求参数的封装,另一个是返回结果的处理,这两部分都很特别,内容也有点多,这篇主要是处理大体请求流程,所以这两部分的内容就放到下一篇了!工作中,这两部分的技术还是很经典的,在工作中还是经常会用到。

方法的调用完成之后,我们就返回到了最开始的调用invokeHandlerMethod#invokeHandlerMethod()方法的最后一行(已经找不到方法的道友,请看ha.handle()这节开始),该行返回了一个getModelAndView();

getModelAndView

// RequestMappingHandlerAdapter.class
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest)
throws Exception
{

modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
// 如果标记已经处理完成,就返回null
return null;
}
//后续逻辑尝试返回一个mav视图
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
...
return mav;
}

这个方法的具体逻辑就是我们接口调用完之后处理一些必要事情,你可以理解为清理现场。第一个if判断一个标记,如果已经处理完成就返回null,这个标记就是在对返回值处理的逻辑中设置的。比如我们@ResponseBody注解的意义就是告诉spring我要返回数据,而不是返回一个页面,那么在返回值处理完之后,数据就已经写入到response的输出流里面了,这个请求就算完成了,所以就会给spring一个标记,告诉它这个请求已经ojbk了。

至此,ha.handle()方法就算结束了,饶了一圈,我们终于又回到了DispatcherServlet#dispatch()了,紧接着调用的就是拦截器的后置处理,

mappedHandler.applyPostHandle(processedRequest, response, mv)

这个方法特别简单,也是直接略过,直接来看来看这个类的最后一个方法。

processDispatchResult()

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;

if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//调用HandlerExceptionResolver处理异常,
//这个东西就是在servlet初始化的时候最后一步initStrategies()方法当中,跟requestMapping等一起注册的
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
//如果返回的mv不为空,处理视图,并跳转,这里会用到viewResolver
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
...
if (mappedHandler != null) {
//这个方法里面会调用Intecepter的afterCompletion()方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

在最后的处理结果当中,如果请求有异常发生,就会导向异常处理器去处理,如果没有,就会根据是否存在视图进行跳转,最后调用Interceptor#afterCompletion()方法。到这里整个请求算完了,剩下的就是一些后续清理工作,发布时间通知等工作了。

到这里流程上已经是差不多了,给各位道友贴一个方法调用链,希望各位享用愉快!

Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

reqeust_sequence.png

忍不住的剧透: 如果你还在用Aop拦截Controller层来实现请求日志拦截,那等你了解完在下的请求参数处理以及返回值处理之后,你一定可以换一种方式去实现,用实力,亮瞎你队友的双眼!明人不装暗B,在下已是很久没有主动去用过AOP了,因为在很多时候都有其他方式可以实现。

Good Luck …


原文始发于微信公众号(心猿易码):Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133297.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!