DispatcherServlet源码分析
一、客户端发送请求的总体过程
DispatcherServlet是SpringMVC的入口,DispatcherServlet其实也是一个Servlet。服务器处理客户端请求的步骤如下:
1、客户端发送请求的时候,会调用Servlet对应的doGet、doPost、doDelete等方法。
2、上面的方法会调用processRequest方法
3、processRequest方法进一步调用doService方法
4、DispatcherServlet实现了doService方法,在doService方法中对Request参数进行处理,然后调用doDispatch方法
5、在doDispatch方法中获取并调用处理器映射器、处理器适配器,获取并返回执行结果。
DispatcherServlet是Web中处于比较核心的位置,被称为前端控制器。SpringMVC中常用的几个概念,处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)和视图解析器(ViewResolver)都在DispatcherServlet的doDispatch中有所体现。
通过调用getHandler方法获取Handler对象,getHandler方法会调用HandlerMapping,通过请求的路径查找Handler;返回值是一个HandlerExecutionChain对象,其中不只包含了Handler对象,还包含一个HandlerInterceptor(拦截器)的链表。
Handler需要借助于HandlerAdapter来执行,doDispatch调用getHandlerAdapter方法查找具体的HandlerAdapter。Spring容器中可能配置了多个HandlerAdapter,具体哪个HandlerAdapter能够处理当前的Handler,是根据HandlerAdapter的supports方法来查找可以处理该Handler的HandlerAdapter。
之后会调用拦截器的preHandler方法,HandlerAdapter会处理具体的Handler,调用拦截器的postHandler方法。
然后,doDispatch会调用processDispatchResult方法,在processDispatchResult方法中,在其中的render方法中,会循环ViewResolver,确定哪个ViewResolver可以解析对应view,然后调用view的render方法进行渲染。processDispatchResult方法还会调用拦截器的afterCompletion方法。
二、处理器映射器
通过调用处理器映射器,得到请求路径对应的处理器。如果没有找到处理器,则在Response中返回错误信息,该方法直接退出。
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
getHandler方法的代码如下,实质就是循环便利所有处理器映射器,找到与请求路径相匹配的处理器映射器。
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
三、判断是否网页是否需要重新生成
HTTP协议允许只发送带有Last-Modified的表头信息到服务器端,服务器端判断本地的信息是否修改了,如果没修改,最后的时间将与Last-Modified一致,此时不需要服务器端再生成信息,直接告诉浏览器信息没有改变,使用其本地数据即可。
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
四、拦截器
在从处理器映射器获取对应的处理器的时候(通过DispatcherServlet的getHandler方法),返回的不是处理器对象,而是一个HandlerExecutionChain,这个HandlerExecutionChain中包含Handler对象;同时还包含一个HandlerInterceptor链表,而HandlerInterceptor就是拦截器。
而对于HandlerInterceptor接口中定义的三个方法中,preHandler在handler的执行前被调用;而postHandler在handler执行后,在进行视图解析之前执行;afterCompletion在view渲染完成、在DispatcherServlet返回之前执行。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
上面的代码中,mappedHandler.applyPreHandle(processedRequest, response)是执行拦截器的preHandler;而mappedHandler.applyPostHandle(processedRequest, response, mv)则执行拦截器的postHandler方法。而拦截器的afterCompletion方法则是在processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)方法中执行的,位置是在该方法的最后,view渲染完成后。
PS:我们需要注意的是:当preHandler返回false时,当前的请求将在执行完afterCompletion后直接返回,handler也将不会执行。源码如下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (getInterceptors() != null) {
for (int i = 0; i < getInterceptors().length; i++) {
HandlerInterceptor interceptor = getInterceptors()[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
需要注意的是上面的interceptorIndex变量,它保存的是刚刚已经执行过的拦截器。而一旦拦截器的preHandler返回false,就会进入triggerAfterCompletion方法,该方法会执行拦截器的afterCompletion方法。但是不可能把所有拦截器的afterCompletion方法都执行一遍,所以使用interceptorIndex进行记录,就可以很方便的知道都有哪些拦截器执行了preHandler方法,调用他们的afterCompletion就可以了。
另外需要注意的地方是,设置interceptorIndex的位置是在循环的最后,也就是说,此处记录的是preHandler返回true的那个拦截器对应的index,也就是说返回false的拦截器的afterCompletion方法不会被调用。
五、处理器适配器
通过HandlerExecutionChain.getHandler返回处理器对象,getHandler返回的是Object对象。DispatcherServlet通过getHandlerAdapter方法找到与Handler匹配的HandlerAdapter类,再通过这个HandlerAdapter去执行Handler对应的方法。
获取Handler匹配的HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
HandlerAdapter去执行Handler对应方法,其中返回的mv是ModelAndView对象:
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
六、视图解析器
DispatcherServlet在processDispatchResult中解析并返回View,在render方法中会根据ModelAndView渲染产生View对象。
DispatcherServlet源码分析的更多相关文章
- 【Spring】DispatcherServlet源码分析
使用过HttpServlet的都应该用过其doGet和doPost方法,接下来看看DispatcherServlet对这两个方法的实现(源码在DispatcherServlet的父类Framework ...
- springMVC源码分析--DispatcherServlet请求获取及处理
在之前的博客springMVC源码分析--容器初始化(二)DispatcherServlet中我们介绍过DispatcherServlet,是在容器初始化过程中出现的,我们之前也说过Dispatche ...
- SpringMVC源码分析--容器初始化(五)DispatcherServlet
上一篇博客SpringMVC源码分析--容器初始化(四)FrameworkServlet我们已经了解到了SpringMVC容器的初始化,SpringMVC对容器初始化后会进行一系列的其他属性的初始化操 ...
- springMVC源码分析--容器初始化(二)DispatcherServlet
在上一篇博客springMVC源码分析--容器初始化(一)中我们介绍了spring web初始化IOC容器的过程,springMVC作为spring项目中的子项目,其可以和spring web容器很好 ...
- SpringMVC源码分析(3)DispatcherServlet的请求处理流程
<springmvc源码分析(2)dispatcherservlet的初始化>初始化DispatcherServlet的多个组件. 本文继续分析DispatcherServlet解析请求的 ...
- 6、SpringMVC源码分析(1):分析DispatcherServlet.doDispatch方法,了解总体流程
所有的http请求都会交给DispatcherServlet类的doDispatch方法进行处理,将DispatcherServlet.doDispatch函数的javadoc复制到下面: /* * ...
- 深入理解Spring之九:DispatcherServlet初始化源码分析
转载 https://mp.weixin.qq.com/s/UF9s52CBzEDmD0bwMfFw9A DispatcherServlet是SpringMVC的核心分发器,它实现了请求分发,是处理请 ...
- SpringMVC源码分析4:DispatcherServlet如何找到正确的Controller
SpringMVC是目前主流的Web MVC框架之一. 我们使用浏览器通过地址 http://ip:port/contextPath/path进行访问,SpringMVC是如何得知用户到底是访问哪个 ...
- 深入理解 spring 容器,源码分析加载过程
Spring框架提供了构建Web应用程序的全功能MVC模块,叫Spring MVC,通过Spring Core+Spring MVC即可搭建一套稳定的Java Web项目.本文通过Spring MVC ...
随机推荐
- Android 去掉ScrollView、GridView、ListView向上 滑动时顶部的投影/阴影
在ScrollView.GridView.ListView向上滑动的过程中,这些控件的顶部会出现一个系统默认的白色阴影,有些时候这个白色的阴影看上去好看,那么就需要除去. 去掉方法:在ScrollVi ...
- 洛谷 P2256 一中校运会之百米跑
题目链接 https://www.luogu.org/problemnew/show/P2256 题目背景 在一大堆秀恩爱的**之中,来不及秀恩爱的苏大学神踏着坚定(?)的步伐走向了100米跑的起点. ...
- wifidog源码分析 - wifidog原理
wifidog是一个用于配合认证服务器实现无线网页认证功能的程序,常见的情景就是使用于公共场合的无线wifi接入点,首先移动设备会连接公共wifi接入点,之后会弹出网页要求输入用户名密码,认证过后才能 ...
- jdk1.8安装后查看Java -version出错。
最近在电脑行安装了多个jdk的版本 分别是jdk1.6,jdk1.7,jdk1.8三个版本,在配置环境变量的时候,选择的是jdk1.7; 但是奇怪的是,当我在cmd中输入java -version后, ...
- Luogu3320 SDOI2015 寻宝游戏 链并
传送门 可以发现从哪里开始的最优答案都是一样的.我们只需要用一种比较好维护的方法维护答案就好了. 我们考虑用$dfs$序加上$set$维护链并.先预处理$dfs$序,将当前有宝藏的点丢入$set$中, ...
- sun.misc.BASE64Decoder 限制取消
sun.misc.BASE64Decoder Windows -> Preferences -> Java -> Compiler -> Errors/Warnings -&g ...
- Python 学习 第一篇:数据类型(数字,集合,布尔类型,操作符)
Python语言最常用的对象是变量和常量,常量的值是字面意思,其值是不可变的,变量的值是可变的,例如,123,"上海"是常量,而a=1,a=2,其中a是变量名.内置的核心数据类型有 ...
- .net core实践系列之短信服务-为什么选择.net core(开篇)
前言 从今天我将会写.net core实战系列,以我最近完成的短信服务作为例子.该系列将会尽量以最短的时间全部发布出来.源码也将优先开源出来给大家. 源码地址:https://github.com/S ...
- REST-framework快速构建API--源码解析
一.APIView 通过APIView实现API的过程如下: urls.py url(r'^books/$', views.BookView.as_view(),name="books&qu ...
- kvm虚拟化管理平台WebVirtMgr部署-完整记录(1)
公司机房有一台2U的服务器(64G内存,32核),由于近期新增业务比较多,测试机也要新增,服务器资源十分有限.所以打算在这台2U服务器上部署kvm虚拟化,虚出多台VM出来,以应对新的测试需求.当KVM ...