SpringMVC源码情操陶冶-DispatcherServlet简析(二)
承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求
DispatcherServlet#doDispatch()
DispatcherServlet
复写父类的doService()
方法,其中最主要的处理客户端发的请求便是doDispath()
方法,我们只深究此方法,大致上看下其中的逻辑
注释瞧一发
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
由官方注释中可得出以下结论:
所有的关于
http
协议的方法都是通过本方法来处理处理过程中,
handler
处理器是核心,优先是从HandlerMappings
中获取,再而可通过HandlerAdapter
适配器来再一层包装供支持更多形式的请求
源码简析
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//这里的processedRequest主要用于文件上传类的请求
HttpServletRequest processedRequest = request;
//mappedHandler是处理的核心,此处可以理解为处理链
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//视图对象
ModelAndView mv = null;
//异常对象
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.优先从HandlerMappings中获取处理器对象
mappedHandler = getHandler(processedRequest);
//这里找不到handler则会出现我们熟悉的"No mapping found"日志打印
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//返回404错误
noHandlerFound(processedRequest, response);
return;
}
//HandlerAdapter必须拥有,否则会抛异常
//最终通过此对象来获取视图对象
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)) {
//获取上次修改事时间,第一次访问为-1L
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
//对未修改的资源get请求,直接返回302状态码
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//拦截器处理请求,调用拦截接口preHandle方法,一旦HandlerInteceptor返回false,则表示拦截成功
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.通过HandlerAdapter处理请求返回逻辑试图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//异步请求直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//倘若mv对象内部没有逻辑视图名则采取默认视图
applyDefaultViewName(processedRequest, mv);
//调用拦截器接口的postHandle()接口
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
//处理过程中出现了异常
dispatchException = ex;
}
//再次处理,如果有异常出现则需要处理异常信息,因为异常也可有对应的视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//processDispatchResult()出现异常时调用
//倒序调用之前已调用过的HandlerInteceptor接口的afterCompletion()方法,再直接返回异常信息给客户端
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
//processDispatchResult()出现异常时调用
//倒序调用之前已调用过的HandlerInteceptor接口的afterCompletion()方法,再直接返回异常信息给客户端
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
//调用所有的实现了AsyncHandlerInterceptor接口的afterConcurrentHandlingStarted()方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
//清除文件上传请求的相关信息,释放资源
cleanupMultipart(processedRequest);
}
}
}
}
基本操作逻辑为:
根据请求路径查找是否存在某个
HandlerMapping
与之对应根据
HandlerMapping
获取HandlerExecutionChain
处理链,内部包含一系列的拦截器以及真实处理的Handler
对象对处理链返回的Handler或者其本身也为空,则直接返回404错误
根据
HandlerExecutionChain
处理链中的Handler
对象获取HandlerAdapter
适配器,如果没有找到则直接返回异常对GET/HEAD请求做
Last-Modified
校验,如果是第二次重复请求则直接返回302状态使用
HandlerExecutionChain
处理链中的interceptors
拦截器依次调用preHandler()
预拦截方法,如果拦截器执行过程中出现return false
的情况,则直接被拦截返回通过
HandlerAdapter
适配器的handle()
方法返回视图对象使用
HandlerExecutionChain
处理链中的interceptors
拦截器依次调用postHandler()
方法,对返回内容进行补充对视图对象进行渲染返回
如果在处理过程中出现了异常,则对异常进行相应的抛出或者异常视图的渲染即可能直接返回粗暴的异常信息或者友好的错误信息页面
DispatcherServlet#getHandler()-获取HandlerExecutionChain处理链
简单代码如下
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
//根据HandlerMapping对象获取处理链
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
主要是注意此处的handlerMappings
属性,根据我们在springmvc配置中常用mvc:annotation-driven
节点,我们可以得知包含的属性主要有
- RequestMappingHandlerMapping 主要解析
@Controller
注解类并解析其中包含@RequestMapping
的注解方法包装为HandlerMethod
作为handler
对象 - BeanNameUrlHandlerMapping 对含有
/
开头的beanName注册为handler
,handler
对象则为其本身在springmvc上下文中对应的class类实例 - SimpleUrlHandlerMapping 即采用
urlMap
保存路径与处理类的关系,即可以直接指定url对应handler
对象,其中handler
对象多为beanName对应的class类【此处不包含】
具体
HanlderMapping
获取处理链对象是通过抽象类AbstractHandlerMapping#getHandler()
来操作的,限于篇幅过长,遂独立成篇>>>SpringMVC源码情操陶冶-AbstractHandlerMapping
DispatcherServlet#getHandlerAdapter()-获取Handler适配器
获取适配器的目的是为了通过其获取视图对象
//参数handler一般为具体对象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//此处的handler可为HandlerMethod/HttpRequestHandler/Controller/Servlet
for (HandlerAdapter ha : this.handlerAdapters) {
//supports方法代表其支持何种handler对象,也就是适配器的意义所在
if (ha.supports(handler)) {
return ha;
}
}
//此处可知当HandlerAdapter没有找到则会抛出ServletException异常
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
HandlerExecutionChain#applyPreHandle()-对请求进行拦截的预处理
调用对应请求路径的拦截器集合的统一方法,源码奉上
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取内部的HandlerInterceptor集合
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//依次执行
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
//调用拦截器的preHandle()方法,一旦返回false则提前结束请求
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
执行对应请求路径的拦截器集合的统一方法
preHandler()
方法,拦截器一般都是HandlerInterceptor
接口的实现类一旦顺序执行过程中,出现
preHandler()
方法中返回false,则表示直接返回,true代表往下执行下一个拦截器
HandlerAdapter#handle()-对请求进行业务处理并返回视图对象
限于篇幅过长,具体可见>>>SpringMVC源码情操陶冶-HandlerAdapter适配器简析
DispatcherServlet#processDispatchResult()-解析视图对象返回结果
主要处理视图和异常视图,具体源码奉上
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
//对存在异常的优先处理
if (exception != null) {
//是否为ModelAndViewDefinitionException异常
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//根据异常解析类来解析特定的异常
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//对视图进行渲染
render(mv, request, response);
//针对异常的视图,清除request对象中的error属性
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
//执行拦截器中的afterCompletion()方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
由以上代码可知springmvc优先对异常作视图解析,然后通过render()
方法渲染视图返回给前台。这其中也会调用拦截器的afterCompletion()
方法来收尾
DispatcherServlet#processHandlerException()-处理异常返回ModelAndView
通过已注册的HandlerExceptionResolver
集合来解析异常返回视图,源码奉上
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//遍历异常类解析器集合,解析成功则返回
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
//解析器解析异常,查找是否有配备的异常视图
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
//判断model和view是否为空
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
//尝试设置默认的view对象,默认操作为
//比如"/view/req"-->设置"view/req"为viewName
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
//将异常信息保存至request对象中
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
//如果解析器没有解析到合适的视图对象则直接抛出异常
throw ex;
}
HandlerExceptionResolver
对象处理异常信息的解析,主要查找是否有具体的视图绑定到该异常,具体可查看>>>SpringMVC源码情操陶冶-AbstractHandlerExceptionResolver当查找到的
ModelAndView
对象不含有view对象但含有model对象时,其会尝试获取默认视图,比如请求路径为"/view/req"-->设置"view/req"为viewName当查找不到
ModelAndView
对象时则直接返回异常,这将导致前端页面会展示具体的异常信息
DispatcherServlet#render()-渲染视图
渲染视图返回给前端页面,具体源码奉上
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
//判断mv内部属性view是否为string类型
if (mv.isReference()) {
// 根据mv通过viewResolvers集合(FreemarkerResolver/VelocityResolver..)解析获取视图对象
// 包括寻找页面
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
//表明存储在ModelAndView对象内部的view要么是String.class类型要么是View.class类型
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
try {
//最后才是对视图的渲染,返回具体页面给前台
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
//渲染失败仍会抛出异常直接返回给前端
throw ex;
}
}
ModelAndView对象中的内部属性
view
要么是String.class类型要么是View.class接口类型,两者选其一针对
view
类型为String.class的,通过springmvc上下文已注册的ViewResolver
集合,调用其统一一接口方法resolveViewName()
方法来获取View对象。具体>>>SpringMVC源码情操陶冶-ViewResolver视图解析最后渲染视图,并将model等属性绑定到页面引擎中,比如freemarker/groovy等,具体>>>SpringMVC源码情操陶冶-View视图渲染
当然渲染失败,也会直接将异常显示给前端页面
小结
本文只对springmvc如何处理请求并返回作下简单的简析
SpringMVC源码情操陶冶-DispatcherServlet简析(二)的更多相关文章
- SpringMVC源码情操陶冶-DispatcherServlet
本文对springmvc核心类DispatcherServlet作下简单的向导,方便博主与读者查阅 DispatcherServlet-继承关系 分析DispatcherServlet的继承关系以及主 ...
- SpringMVC源码情操陶冶-DispatcherServlet父类简析
阅读源码有助于陶冶情操,本文对springmvc作个简单的向导 springmvc-web.xml配置 <servlet> <servlet-name>dispatch< ...
- SpringMVC源码情操陶冶-DispatcherServlet类简析(一)
阅读源码有利于陶冶情操,此文承接前文SpringMVC源码情操陶冶-DispatcherServlet父类简析 注意:springmvc初始化其他内容,其对应的配置文件已被加载至beanFactory ...
- SpringMVC源码情操陶冶-RequestMappingHandlerAdapter适配器
承接前文SpringMVC源码情操陶冶-HandlerAdapter适配器简析.RequestMappingHandlerAdapter适配器组件是专门处理RequestMappingHandlerM ...
- SpringMVC源码情操陶冶-HandlerAdapter适配器简析
springmvc中对业务的具体处理是通过HandlerAdapter适配器操作的 HandlerAdapter接口方法 列表如下 /** * Given a handler instance, re ...
- SpringMVC源码情操陶冶-FreeMarker之web配置
前言:本文不讲解FreeMarkerView视图的相关配置,其配置基本由FreeMarkerViewResolver实现,具体可参考>>>SpringMVC源码情操陶冶-ViewRe ...
- SpringMVC源码情操陶冶-AnnotationDrivenBeanDefinitionParser注解解析器
mvc:annotation-driven节点的解析器,是springmvc的核心解析器 官方注释 Open Declaration org.springframework.web.servlet.c ...
- SpringMVC源码情操陶冶-AbstractHandlerMethodMapping
承接前文SpringMVC源码情操陶冶-AbstractHandlerMapping,本文将介绍如何注册HandlerMethod对象作为handler 类结构瞧一瞧 public abstract ...
- SpringMVC源码情操陶冶-AbstractUrlHandlerMapping
承接前文SpringMVC源码情操陶冶-AbstractHandlerMapping,前文主要讲解了如何获取handler处理对象,本文将针对beanName注册为handler对象作下解析 Abst ...
随机推荐
- noip级别数论?
TAT快noip了才开始去接触数论(真心不敢学..)这里做一下整理吧(都是些定义之类的东西= =) 欧几里德:gcd(a,b)=gcd(b,a%b);具体证明见百科? 扩展欧几里德: 求a*x+b*y ...
- 2017ecjtu-summer training # 11 POJ 2492
A Bug's Life Time Limit: 10000MS Memory Limit: 65536K Total Submissions: 38280 Accepted: 12452 D ...
- Ping pong(树状数组求序列中比某个位置上的数小的数字个数)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2492 Ping pong Time Limit: 2000/1000 MS (Java/Others) ...
- Linux shell编程命令-Linux基础环境命令学习笔记
1.正则表达式 1)^开始 *前一个字符重复0次以上 + 1次以上 ? 0次或者1次 . 一个任意字符(.*连用) {m,n} m到n次 [0-9][a-z] 任意数字或字母 $结束字符 2)sed和 ...
- SQL强化(三) 自定义函数
---恢复内容开始--- Oracle中我们可以通过自定义函数去做一些逻辑判断,这样可以减少查询语句,提高开发效率 create -- 创建自定义函数 or replace -- 有同名函数就替换, ...
- CSS中的定位与浮动
CSS中的定位与浮动 本文主要讲述CSS中的三种定位样式static.relative和absolute的区别以及浮动元素的特征. 定位样式 CSS中定位样式position的取值有三个,默认值:st ...
- vue-router自动判断左右翻页转场动画
前段时间做了一个移动端spa项目,技术基于 :vue + vue-router + vuex + mint-ui 因为使用了vue-cli脚手架的webpack模版,所有页面都以.vue为后缀的文件作 ...
- 解决DEDECMS Call to undefined function dede_htmlspecialchars()
作者:DEDECMS建站网 关注: 3610 时间:2015-11-18 16:39 内容详情 以下内容您可能感兴趣: 织梦官方在2015年6月18日更新了织梦5.7,为了兼容php5.4+,修改了/ ...
- [机器学习]模型评价参数,准确率,召回率,F1-score
很久很久以前,我还是有个建筑梦的大二少年,有一天,讲图的老师看了眼我的设计图,说:"我觉得你这个设计做得很紧张".当时我就崩溃,对紧张不紧张这样的评价标准理解无能.多年后我终于明白 ...
- Node.js进阶:5分钟入门非对称加密方法
前言 刚回答了SegmentFault上一个兄弟提的问题<非对称解密出错>.这个属于Node.js在安全上的应用,遇到同样问题的人应该不少,基于回答的问题,这里简单总结下. 非对称加密的理 ...