SpringMVC源码分析(3)DispatcherServlet的请求处理流程
<springmvc源码分析(2)dispatcherservlet的初始化>初始化DispatcherServlet的多个组件。
本文继续分析DispatcherServlet解析请求的过程。
概览

①:DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件.
②:HanlerMapping是springmvc中完成url到controller映射的组件.DispatcherServlet接收request,然后从HandlerMapping查找处理request的controller.
③:Cntroller处理request,并返回ModelAndView对象,Controller是springmvc中负责处理request的组件(类似于struts2中的Action),ModelAndView是封装结果视图的组件.
④ ⑤ ⑥:视图解析器解析ModelAndView对象并返回对应的视图给客户端.
要点
维护url和controller的映射
这部分工作由DefaultAnnotationHandlerMapping.setApplicationContext的父类
org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping.initApplicationContext实现。具体方法为detectHandlers
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
protectedvoiddetectHandlers()throwsBeansException{if(logger.isDebugEnabled()){logger.debug("LookingforURLmappingsinapplicationcontext:"+getApplicationContext());}String[]beanNames=(this.detectHandlersInAncestorContexts?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(),Object.class):getApplicationContext().getBeanNamesForType(Object.class));//TakeanybeannamethatwecandetermineURLsfor.for(StringbeanName:beanNames){String[]urls=determineUrlsForHandler(beanName);if(!ObjectUtils.isEmpty(urls)){//URLpathsfound:Let'sconsideritahandler.registerHandler(urls,beanName);}else{if(logger.isDebugEnabled()){logger.debug("Rejectedbeanname'"+beanName+"':noURLpathsidentified");}}}} |
2.准确定位处理请求的具体方法(在AnnotationMethodHandlerAdapter中实现)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protectedModelAndViewinvokeHandlerMethod(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{ServletHandlerMethodResolvermethodResolver=getMethodResolver(handler);MethodhandlerMethod=methodResolver.resolveHandlerMethod(request);//具体实现方法的匹配ServletHandlerMethodInvokermethodInvoker=newServletHandlerMethodInvoker(methodResolver);ServletWebRequestwebRequest=newServletWebRequest(request,response);ExtendedModelMapimplicitModel=newBindingAwareModelMap();Objectresult=methodInvoker.invokeHandlerMethod(handlerMethod,handler,webRequest,implicitModel);ModelAndViewmav=methodInvoker.getModelAndView(handlerMethod,handler.getClass(),result,implicitModel,webRequest);methodInvoker.updateModelAttributes(handler,(mav!=null?mav.getModel():null),implicitModel,webRequest);returnmav;} |
1.请求入口
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
@OverrideprotectedfinalvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{processRequest(request,response);}/***DelegatePOSTrequeststo{@link#processRequest}.*@see#doService*/@OverrideprotectedfinalvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{processRequest(request,response);}protectedfinalvoidprocessRequest(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{longstartTime=System.currentTimeMillis();ThrowablefailureCause=null;//ExposecurrentLocaleResolverandrequestasLocaleContext.LocaleContextpreviousLocaleContext=LocaleContextHolder.getLocaleContext();LocaleContextHolder.setLocaleContext(buildLocaleContext(request),this.threadContextInheritable);//ExposecurrentRequestAttributestocurrentthread.RequestAttributespreviousRequestAttributes=RequestContextHolder.getRequestAttributes();ServletRequestAttributesrequestAttributes=null;if(previousRequestAttributes==null||previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)){requestAttributes=newServletRequestAttributes(request);RequestContextHolder.setRequestAttributes(requestAttributes,this.threadContextInheritable);}if(logger.isTraceEnabled()){logger.trace("Boundrequestcontexttothread:"+request);}try{doService(request,response);}catch(ServletExceptionex){failureCause=ex;throwex;}catch(IOExceptionex){failureCause=ex;throwex;}catch(Throwableex){failureCause=ex;thrownewNestedServletException("Requestprocessingfailed",ex);}finally{//Clearrequestattributesandresetthread-boundcontext.LocaleContextHolder.setLocaleContext(previousLocaleContext,this.threadContextInheritable);if(requestAttributes!=null){RequestContextHolder.setRequestAttributes(previousRequestAttributes,this.threadContextInheritable);requestAttributes.requestCompleted();}if(logger.isTraceEnabled()){logger.trace("Clearedthread-boundrequestcontext:"+request);}if(failureCause!=null){this.logger.debug("Couldnotcompleterequest",failureCause);}else{this.logger.debug("Successfullycompletedrequest");}if(this.publishEvents){//Whetherornotwesucceeded,publishanevent.longprocessingTime=System.currentTimeMillis()-startTime;this.webApplicationContext.publishEvent(newServletRequestHandledEvent(this,request.getRequestURI(),request.getRemoteAddr(),request.getMethod(),getServletConfig().getServletName(),WebUtils.getSessionId(request),getUsernameForRequest(request),processingTime,failureCause));}}} |
processRequest方法主要做4项工作。
得到当前线程的LocaleContext和RequestAttributes,创建新的LocaleContext和RequestAttributes并重新绑定到当前线程。
调用子类实现的doService()
重置当前线程的LocaleContext和RequestAttributes
执行成功后,发布ServletRequestHandledEvent事件。
2.DispatcherServlet自定义的doService方法
1234567891011121314151617181920212223242526272829303132333435363738protectedvoiddoService(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{if(logger.isDebugEnabled()){StringrequestUri=urlPathHelper.getRequestUri(request);logger.debug("DispatcherServletwithname'"+getServletName()+"'processing"+request.getMethod()+"requestfor["+requestUri+"]");}//Keepasnapshotoftherequestattributesincaseofaninclude,//tobeabletorestoretheoriginalattributesaftertheinclude.Map<string,object>attributesSnapshot=null;if(WebUtils.isIncludeRequest(request)){logger.debug("Takingsnapshotofrequestattributesbeforeinclude");attributesSnapshot=newHashMap<string,object>();EnumerationattrNames=request.getAttributeNames();while(attrNames.hasMoreElements()){StringattrName=(String)attrNames.nextElement();if(this.cleanupAfterInclude||attrName.startsWith("org.springframework.web.servlet")){attributesSnapshot.put(attrName,request.getAttribute(attrName));}}}//Makeframeworkobjectsavailabletohandlersandviewobjects.request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE,this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE,getThemeSource());try{doDispatch(request,response);}finally{//Restoretheoriginalattributesnapshot,incaseofaninclude.if(attributesSnapshot!=null){restoreAttributesAfterInclude(request,attributesSnapshot);}}}</string,object></string,object>主要做两部分工作
如果是include请求,先保存一份request域数据的快照,doDispatch执行过后,将会用快照数据恢复。
调用doDispatch方法,完成请求转发。
3.doDispatch方法
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{HttpServletRequestprocessedRequest=request;HandlerExecutionChainmappedHandler=null;intinterceptorIndex=-1;try{ModelAndViewmv;booleanerrorView=false;try{//1.检查是否是文件上传的请求processedRequest=checkMultipart(request);//Determinehandlerforthecurrentrequest.//2.取得处理当前请求的controller,这里也称为hanlder,处理器,第一个步骤的意义就在这里体现了.//这里并不是直接返回controller,而是返回的HandlerExecutionChain请求处理器链对象,该对象封装了handler和interceptors.mappedHandler=getHandler(processedRequest,false);if(mappedHandler==null||mappedHandler.getHandler()==null){noHandlerFound(processedRequest,response);return;}//Determinehandleradapterforthecurrentrequest.//3.获取处理request的处理器适配器handleradapterHandlerAdapterha=getHandlerAdapter(mappedHandler.getHandler());//Processlast-modifiedheader,ifsupportedbythehandler.Stringmethod=request.getMethod();booleanisGet="GET".equals(method);if(isGet||"HEAD".equals(method)){longlastModified=ha.getLastModified(request,mappedHandler.getHandler());if(logger.isDebugEnabled()){StringrequestUri=urlPathHelper.getRequestUri(request);logger.debug("Last-Modifiedvaluefor["+requestUri+"]is:"+lastModified);}if(newServletWebRequest(request,response).checkNotModified(lastModified)&&isGet){return;}}//ApplypreHandlemethodsofregisteredinterceptors.//4.拦截器的预处理方法HandlerInterceptor[]interceptors=mappedHandler.getInterceptors();if(interceptors!=null){for(inti=0;i<interceptors.length;i++){ actuallyinvokethehandler.=""applyposthandlemethodsofregisteredinterceptors.=""handlerinterceptorinterceptor="interceptors[i];"interceptorindex="i;"inti="interceptors.length-1;i"mv="ha.handle(processedRequest,response,mappedHandler.getHandler());">=0;i--){HandlerInterceptorinterceptor=interceptors[i];interceptor.postHandle(processedRequest,response,mappedHandler.getHandler(),mv);}}}catch(ModelAndViewDefiningExceptionex){logger.debug("ModelAndViewDefiningExceptionencountered",ex);mv=ex.getModelAndView();}catch(Exceptionex){Objecthandler=(mappedHandler!=null?mappedHandler.getHandler():null);mv=processHandlerException(processedRequest,response,handler,ex);errorView=(mv!=null);}//Didthehandlerreturnaviewtorender?if(mv!=null&&!mv.wasCleared()){render(mv,processedRequest,response);if(errorView){WebUtils.clearErrorRequestAttributes(request);}}else{if(logger.isDebugEnabled()){logger.debug("NullModelAndViewreturnedtoDispatcherServletwithname'"+getServletName()+"':assumingHandlerAdaptercompletedrequesthandling");}}//Triggerafter-completionforsuccessfuloutcome.triggerAfterCompletion(mappedHandler,interceptorIndex,processedRequest,response,null);}catch(Exceptionex){//Triggerafter-completionforthrownexception.triggerAfterCompletion(mappedHandler,interceptorIndex,processedRequest,response,ex);throwex;}catch(Errorerr){ServletExceptionex=newNestedServletException("Handlerprocessingfailed",err);//Triggerafter-completionforthrownexception.triggerAfterCompletion(mappedHandler,interceptorIndex,processedRequest,response,ex);throwex;}finally{//Cleanupanyresourcesusedbyamultipartrequest.if(processedRequest!=request){cleanupMultipart(processedRequest);}}}</interceptors.length;i++){>很明显这儿是SpringMVC核心。
1.根据请求的路径找到HandlerMethod(带有Method反射属性,也就是对应Controller中的方法)(DispatcherServlet.getHandler完成)
2.匹配路径对应的拦截器(DispatcherServlet.getHandler完成)
3.获得HandlerExecutionChain对象(DispatcherServlet.getHandler完成)
4.通过HandlerAdapter对象进行处理得到ModelAndView对象(HandlerAdapter.handle)
5.调用HandlerInterceptor.preHandle
6.调用HandlerInterceptor.postHandle
7. 渲染
4.总结

简单粗暴的总结下
step1-6: 获取controller
step5-15 :调用controller方法
step17-20:渲染view
其他:aop方式处理拦截统一处理。
SpringMVC源码分析(3)DispatcherServlet的请求处理流程的更多相关文章
- 2.SpringMVC源码分析:DispatcherServlet的初始化与请求转发
一.DispatcherServlet的初始化 在我们第一次学Servlet编程,学java web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根 ...
- springmvc源码分析系列-请求处理流程
接上一篇-springmvc源码分析开头片 上一节主要说了一下springmvc与struts2的作为MVC中的C(controller)控制层的一些区别及两者在作为控制层方面的一些优缺点.今天就结合 ...
- springMVC源码分析--DispatcherServlet请求获取及处理
在之前的博客springMVC源码分析--容器初始化(二)DispatcherServlet中我们介绍过DispatcherServlet,是在容器初始化过程中出现的,我们之前也说过Dispatche ...
- SpringMVC源码分析--容器初始化(五)DispatcherServlet
上一篇博客SpringMVC源码分析--容器初始化(四)FrameworkServlet我们已经了解到了SpringMVC容器的初始化,SpringMVC对容器初始化后会进行一系列的其他属性的初始化操 ...
- springMVC源码分析--容器初始化(二)DispatcherServlet
在上一篇博客springMVC源码分析--容器初始化(一)中我们介绍了spring web初始化IOC容器的过程,springMVC作为spring项目中的子项目,其可以和spring web容器很好 ...
- 8、SpringMVC源码分析(3):分析ModelAndView的形成过程
首先,我们还是从DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response) throw ...
- SpringMVC源码情操陶冶-DispatcherServlet类简析(一)
阅读源码有利于陶冶情操,此文承接前文SpringMVC源码情操陶冶-DispatcherServlet父类简析 注意:springmvc初始化其他内容,其对应的配置文件已被加载至beanFactory ...
- 框架-springmvc源码分析(一)
框架-springmvc源码分析(一) 参考: http://www.cnblogs.com/heavenyes/p/3905844.html#a1 https://www.cnblogs.com/B ...
- [心得体会]SpringMVC源码分析
1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...
随机推荐
- Vsphere初试——使用Vsphere client
好不容易安装好ESXi之后,就要安装一个Vsphere Client,为什么要安装这个东东.使用过vmware workstation的人都知道,安装完就可以添加虚拟机,但是ESXi要通过Vspher ...
- Eclipse中配置Tomcat服务器并创建标准Web目录
Eclipse创建 Java Web 项目,并生成标准的目录结构 file --> New --> Dynamic Web project 填写 Project name (该名称项目的名 ...
- The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone问题解决
从错误即可知道是时区的错误,因此只要将时区设置为你当前系统时区即可 因此使用root用户登录mysql,按照如下图所示操作即可. 把时区设置为所在地时区(即东八区的时区)后,再连接数据库就可以了
- Android资源相关语法
2018-08-29 getResources().getString(Rid)获取配置中的字符串
- centos配置虚拟用户再也不用那么麻烦了
http://wiki.centos.org/HowTos/Chroot_Vsftpd_with_non-system_users yum install -y vsftpd db4-utils vs ...
- SpringMVC 学习 八 SSM环境搭建(一) web.xml配置
第一步:导入jar包 注意包的兼容性,以后采用maven会好很多 第二步:配置web.xml 在web.xml中,主要的配置内容有以下几点 (1)spring容器配置文件的位置 <!-- spr ...
- 27 isinstance与issubclass、反射、内置方法
isinstance与issubclass issubclass:判断子类是否属于父类,是则返回True,否则返回False isinstance:判断对象是否属于类,是则返回True,否则返回Fal ...
- php中如何配置项目虚拟路径
php虚拟目录的设置在apache目录下打开conf->httpd.conf文件,找到<IfModule dir_module>,在</IfModule>后面添加如下代码 ...
- 检索 COM 类工厂中 CLSID 为 {10021F00-E260-11CF-AE68-00AA004A34D5} 的组件失败,原因是出现以下错误: 80040154 没有注册类 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))。
ASP.NET利用SQLDMO可以实现在线备份.还原数据库等各种功能. 由于客户的数据库和WEB服务不再同一台服务器,把网站部署在服务器上以后,运行程序,提示如下错误 当使用Interop.SQLDM ...
- 微信小程序之画布
canvas 标签默认宽度300px.高度225px 同一页面中的 canvas-id 不可重复,如果使用一个已经出现过的 canvas-id,该 canvas 标签对应的画布将被隐藏并不再正常工作 ...