上一篇介绍的是init初始化过程,这次继续研究Servlet生命周期中的第二部分,service方法,在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过service()方法,委派到doGet()或者doPost()这些方法,完成Http请求的处理。
一、HttpServlet
首先看一下HttpServlet中的service方法:
1 | protected void service(HttpServletRequest req, HttpServletResponse resp) |
根据不同的请求,它们分别被不同的方法处理。
二、FrameworkServlet
在FrameworkServlet中,处理各种请求的方法都被重写,
1 | /** |
可以看出,这些方法都同意的调用了processRequest方法,而没有进行特殊的处理,注意一点这些方法都是final的,不会再被重写
1 | /** |
整体上分为三个部分:
- 处理前的准备
- doService(request, response);进行处理
- 处理完成后的工作
1、处理过程
这部分有些地方和网上看见的或者书上看见的源码都不一样,应该是新版本进行了改进:
1 | LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); |
不过大体的思路还是一样的,这里涉及几个关键的类和变量:
- LocaleContext和RequestAttributes:
- LocaleContextHolder和RequestContextHolder,持有上面的两个对象,通过get获得,处理之后通过set设置
下面看一下这两个holder对应的抽象类
1 | public abstract class LocaleContextHolder { |
他们内部都包含有ThreadLocal对象,所以,就可以知道整个过程的作用就是分别将这两个东西和请求线程做了绑定。
为了保证当前线程的LocaleContext和RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性
根据当前的request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
doService(request, response)方法,按照一贯套路,肯定又是在子类中重写
请求处理结束后恢复线程到原始状态
请求处理结束后无论成功与否都发布事件通知。每次请求处理结束后,容器上下文都发布了一个ServletRequestHandledEvent事件,可以注册监听器来监听该事件。
可以看到,processRequest()方法只是做了一些线程安全的隔离,真正的请求处理,发生在doService()方法中。
三、DispatcherServlet
1、doService
doService方法在DispatcherServlet中被重写
1 | /** |
几个requet.setAttribute()方法的调用,将前面在初始化流程中实例化的对象设置到http请求的属性中,供下一步处理使用,其中有容器的上下文对象、本地化解析器等SpringMVC特有的编程元素。不同于Struts2中的ValueStack,SpringMVC的数据并没有从HttpServletRequest对象中抽离出来再存进另外一个编程元素,这也跟SpringMVC的设计思想有关。因为从一开始,SpringMVC的设计者就认为,不应该将请求处理过程和Web容器完全隔离。
2、doDispatch
接下来让我们看看doDispatch()这个整个请求转发流程中最核心的方法。DispatcherServlet所接收的Http请求,经过层层转发,最终都是汇总到这个方法中来进行最后的请求分发和处理。doDispatch()这个方法的内容,就是SpringMVC整个框架的精华所在。它通过高度抽象的接口,描述出了一个MVC(Model-View-Controller)设计模式的实现方案。Model、View、Controller三种层次的编程元素,在SpringMVC中都有大量的实现类,各种处理细节也是千差万别。但是,它们最后都是由,也都能由doDispatch()方法来统一描述,这就是接口和抽象的威力,万变不离其宗。
1 | /** |
对应步骤:
- 对MultipartContent类型的Request处理
- 根据request信息寻找对应的Handler
- 没找到对应的Handler的错误处理
- 感觉当前Handler寻找对应的HandlerAdapter
- 缓存处理
- HandlerInterceptor的处理
- 逻辑处理
- 异常视图处理
- 根据视图跳转页面
下面主要分析几个关键的步骤
三、doDispatch
这个过程有几个问题不太明白再详细研究一下。
首先就是关于自己定义的Controller是怎么被执行的,这个要先明白Controller的定义方式,最原始的MVC的做法就是继承Controller接口,当然更高级的就是使用@Controller和@RequestMapping注解。这里先从Controller接口的方法分析,因为这个比较简单。定义一个bean
1 | public class HomeAction implements Controller{ |
1、HandlerMapping和HandlerAdapter
下面再来回顾一下SpringMVC处理请求的流程
用过python Django框架的都知道Django对于访问方式的配置就是,一个url路径和一个函数配对,你访问这个url,就会直接调用这个函数,简单明了。对于java的面向对象来说,就要分两步走。
- 第一步首先要找到是哪个对象,即handler,本工程的handler则是HomeAction对象。
- 第二步要找到访问的函数,即HomeAction的handleRequest方法。
所以就出现了两个接口HandlerMapping和HandlerAdapter,前者负责第一步,后者负责第二步
HandlerMapping的实现类:
- BeanNameUrlHandlerMapping:通过对比url和bean的name找到对应的对象
- SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多
- RequestMappingHandlerMapping :主要是针对注解配置@RequestMapping的,取代了最早的DefaultAnnotationHandlerMapping
HandlerAdapter的实现类:
SimpleControllerHandlerAdapter:要求handler实现Controller接口,该接口的方法为
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response),
也就是上面例子所采用的
HttpRequestHandlerAdapter : 要求handler实现HttpRequestHandler接口,该接口的方法为:
void handleRequest(HttpServletRequest request, HttpServletResponse response)
也就是 handler必须有一个handleRequest方法
RequestMappingHandlerAdapter : 和上面的RequestMappingHandlerMapping配对使用,针对@RequestMapping,取代了最早的AnnotationMethodHandlerAdapter。
先简单的说下这个工程的流程,访问http://localhost:8080/index首先由DispatcherServlet进行转发,通过BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),找到了HomeAction,然后再拿HomeAction和每个adapter进行适配,由于HomeAction实现了Controller接口,所以最终会有SimpleControllerHandlerAdapter来完成对HomeAction的handleRequest方法的调度。然后就顺利的执行了我们想要的方法。
了解了这些实现之后,再来看一下在源码中的实现。
2、HandlerExecutionChain的获取
2.1、getHandler方法
该过程对应的就是这一句
mappedHandler = getHandler(processedRequest);
相关的类在第一篇文章中已经介绍过了,有几点必须清楚:
HandlerMapping是一个接口,DispatcherServlet中持有一个List< HandlerMapping>
获取HandlerExecutionChain的过程就是DispatcherServlet遍历这个List< HandlerMapping>的过程,并将第一个返回结果不为null作为得到的HandlerExecutionChain
上面的说法这个步骤叫作Handler的获取,但其实这个类是HandlerExecutionChain,只不过变量名与handler有关,真正的handler是封装在HandlerExecutionChain里面的。
HandlerExecutionChain mappedHandler = null;
下面看一下具体的代码:
1 | protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
这个就是对应的遍历过程,而hm.getHandler(request)方法是在HandlerMapping接口中定义的,也就是说遍历的这个List中的所有实现类的对象都有这个方法,
2.2、BeanNameUrlHandlerMapping类与getHandler方法
BeanNameUrlHandlerMapping是前面提到的默认加载的HandlerMapping之一,来看一下它的类继承关系,
它的父父父类AbstractHandlerMapping实现了HandlerMapping接口,所以在这里可以看见对HandlerMapping接口方法getHandler的实现。
1 | public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { |
这个方法是final的,说明子类不会再重写,根据代码可以看出,该方法主要分两步:
根据request信息查找对应的handler
这个gethandlerInternal方法主要就是根据url来获取对应的Handler,并且最后返回的handler是一个Object类型的对象。
加入拦截器到执行链
前面介绍过HandlerExecutionChain中,封装了一个handler和n个拦截器,所以在找到对应的handler之后还要加入相应的拦截器。最后封装成HandlerExecutionChain。
经过两个步骤,实现根据输入的request到返回HandlerExecutionChain的过程。
3、HandlerAdapter的获取
3.1、getHandlerAdapter方法
该过程对应的是这句
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
与上面的方法非常类似,也是遍历
1 | /** |
遍历所有的HandlerAdapter,判断他们是否支持这个handler。 这里还是要找到默认配置中三个HandlerAdapter实现类中的一个,SimpleControllerHandlerAdapter,它要求handler实现Controller接口。
3.2、SimpleControllerHandlerAdapter类
因为它和HttpRequestHandlerAdapter的代码都不长,所以就一起贴出来了
1 | package org.springframework.web.servlet.mvc; |
可以看出它们的各个supports方法的唯一区别就是,不同的适配器支持不同的传入的handler类型。
所以再来看一下这个过程就是遍历所有的适配器,找到第一个支持这个handler的适配器,然后返回。
从上面的例子来看,SimpleControllerHandlerAdapter是支持HomeAction的,所以就会返回这个适配器,然后通过它来执行下面的handle方法,也就进入了自己定义的逻辑之中。
3.3、适配器模式
到这里更加理解这个适配器模式了,这个handler的类型有很多种,而最上面的HandlerExecutionChain持有的时候并不知道它是什么类型,因为是Object类型的,它可能是Controller类,也有可能是HttpRequestHandler类,而这些不同的实现类具体的实现方法肯定是不同的,所以,每个实现类都有一个对应的适配器类,得到这个handler的时候遍历所有的适配器,遇到适配器支持的实现类,则返回这个适配器,然后通过这个适配器来调用具体实现类中的方法。
这样对于DispatcherServlet来说,它不知道每个handler具体的实现过程和方法,它需要的就是support和handle方法,至于具体对应到实现类中的哪个方法,则由适配器来决定。
而这个handle方法,则是第7步对应的逻辑处理了。
4、handle方法
在适配器中,handle方法被转换成handler的handleRequest方法。并且返回一个ModelAndView类对象,这里就是执行用户定义的Controller的地方了。
所以这里针对不同方式定义的Controller,执行的方式也是不同的。