本文是Spring MVC源码阅读系列的第二篇,在上一篇Spring MVC的创建过程中我们介绍了SpringMVC的创建过程,本文讲给主要介绍当一个请求到达时,SpringMVC都做了些什么,即Spring MVC是如何处理请求的。
前言
从上一篇Spring MVC的创建过程 我们知道Spring的HttpServletBean继承于HttpServlet,熟悉Servlet编程的同学都知道我们要编写一个新的Servlet只需继承HttpServlet,并重写相应的方法即可,当一个新的请求到来时,首先从Servlet接口的service方法开始,在HttpServlet的service方法中根据请求的类型不同将请求路由到对应的方法,Spring MVC的HttpServlet继承于Httpservlet类,换句话说Spring MVC也是重写了HttpServlet相应的方法来处理请求,还是内有乾坤,下面我们将遵循由整体到局部的学习方式,先了解Spring MVC处理请求的整体过程。
Spring MVC请求处理整体过程
Spring MVC请求处理的大体过程时请求由DispatcherServlet根据处理器映射确定控制器并分配给它,在控制器完成处理后,请求会被发送给一个根据视图解析器确定的视图来呈现输出结果。如下图:
1. web应用服务器接收到一个新请求是,如果匹配DispatcherServlet的请求映射路径,web容器将该请求转发给DispatcherServlet进行处理
2. DispatcherServlet接收到请求后,将根据请求的信息及HandlerMapping的配置找到处理请求的处理器(Handler)
3. 当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler进行封装,以统一的适配器接口调用Handler
4. 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息
5. ModelAndView中包含的是“逻辑视图名”,而非真正的视图对象,DispatcherServlet借助ViewResolver完成逻辑视图名到真实视图名对象的解析工作
6. 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染
7. 最终客户端得到的可能是HTML页面或者其他对象
Spring MVC的请求处理概述
Spring MVC的三个Servlet中的HttpservletBean主要参与了创建工作,没有涉及请求的处理;FrameworkServlet重写了除了doHead的所有处理请求的方法;DispatcherServlet描述了具体处理请求顶层设计结构。
接下来我们先看看FrameworkServlet是如何重写处理请求的方法,又是如何将请求交给DispatcherServlet进行具体的请求处理的。
FrameworkServlet
首先我们先来看看FrameworkServlet的service方法。
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getMethod();
//对PATCH类型请求的处理
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
从上面的代码我们可以看出,FrameworkServlet在service方法中增加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理,即调用HttpServlet的service方法进行处理。为什么要这样做呢,不再调用super.service(),而是直接将请求交给processRequest处理不是更简单吗?从结构上来看确实如此,但是如果这样做的话会存在一些问题。例如:我们为了某种特殊需求需要在Post请求处理前对request进行一些处理,这时可能会新建一个继承自DispatcherServlet的类,然后覆盖doPost方法,在里面对request进行处理,然后再调用super.doPost方法,但是父类根本没有调用doPost,这时就会出现问题,虽然有其他方法来解决这个问题,但按正常的逻辑,调用doPost应该可以完成才合理,而且一般情况下开发者并不需要对Spring MVC 的内部结构非常了解。所以Spring MVC的这种做法是有必要的。从前言我们已经知道HttpServlet的service方法是根据请求类型的不同将请求路由到不同的处理方法的,那么接下来我们来看看doGet方法(其余方法需要自己处理的方法与doGet类似)
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
我们从上面的代码以及其余的doXXX类型方法中可以发现,这里所做的事情与Httpservle里将不同类型的请求路由到不同方法进行处理的思路正好相反,这里又将所有的请求合并到了processRequest方法中。下面我们将一步步解开这神秘的面纱。首先我们先来看看processRequest方法,其代码如下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取LocaleContextHolder中原来保存的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//获取当前请求的LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取RequestContextHolder中原来保存的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//获取当前请求的ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder中
initContextHolders(request, localeContext, requestAttributes);
try {
//具体处理方法入口
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}