承接前文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
*/

由官方注释中可得出以下结论:

  1. 所有的关于http协议的方法都是通过本方法来处理

  2. 处理过程中,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);
}
}
}
}

基本操作逻辑为:

  1. 根据请求路径查找是否存在某个HandlerMapping与之对应

  2. 根据HandlerMapping获取HandlerExecutionChain处理链,内部包含一系列的拦截器以及真实处理的Handler对象

  3. 对处理链返回的Handler或者其本身也为空,则直接返回404错误

  4. 根据HandlerExecutionChain处理链中的Handler对象获取HandlerAdapter适配器,如果没有找到则直接返回异常

  5. 对GET/HEAD请求做Last-Modified校验,如果是第二次重复请求则直接返回302状态

  6. 使用HandlerExecutionChain处理链中的interceptors拦截器依次调用preHandler()预拦截方法,如果拦截器执行过程中出现return false的情况,则直接被拦截返回

  7. 通过HandlerAdapter适配器的handle()方法返回视图对象

  8. 使用HandlerExecutionChain处理链中的interceptors拦截器依次调用postHandler()方法,对返回内容进行补充

  9. 对视图对象进行渲染返回

  10. 如果在处理过程中出现了异常,则对异常进行相应的抛出或者异常视图的渲染即可能直接返回粗暴的异常信息或者友好的错误信息页面

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;
}
}
  1. ModelAndView对象中的内部属性view要么是String.class类型要么是View.class接口类型,两者选其一

  2. 针对view类型为String.class的,通过springmvc上下文已注册的ViewResolver集合,调用其统一一接口方法resolveViewName()方法来获取View对象。具体>>>SpringMVC源码情操陶冶-ViewResolver视图解析

  3. 最后渲染视图,并将model等属性绑定到页面引擎中,比如freemarker/groovy等,具体>>>SpringMVC源码情操陶冶-View视图渲染

  4. 当然渲染失败,也会直接将异常显示给前端页面

小结

本文只对springmvc如何处理请求并返回作下简单的简析

SpringMVC源码情操陶冶-DispatcherServlet简析(二)的更多相关文章

  1. SpringMVC源码情操陶冶-DispatcherServlet

    本文对springmvc核心类DispatcherServlet作下简单的向导,方便博主与读者查阅 DispatcherServlet-继承关系 分析DispatcherServlet的继承关系以及主 ...

  2. SpringMVC源码情操陶冶-DispatcherServlet父类简析

    阅读源码有助于陶冶情操,本文对springmvc作个简单的向导 springmvc-web.xml配置 <servlet> <servlet-name>dispatch< ...

  3. SpringMVC源码情操陶冶-DispatcherServlet类简析(一)

    阅读源码有利于陶冶情操,此文承接前文SpringMVC源码情操陶冶-DispatcherServlet父类简析 注意:springmvc初始化其他内容,其对应的配置文件已被加载至beanFactory ...

  4. SpringMVC源码情操陶冶-RequestMappingHandlerAdapter适配器

    承接前文SpringMVC源码情操陶冶-HandlerAdapter适配器简析.RequestMappingHandlerAdapter适配器组件是专门处理RequestMappingHandlerM ...

  5. SpringMVC源码情操陶冶-HandlerAdapter适配器简析

    springmvc中对业务的具体处理是通过HandlerAdapter适配器操作的 HandlerAdapter接口方法 列表如下 /** * Given a handler instance, re ...

  6. SpringMVC源码情操陶冶-FreeMarker之web配置

    前言:本文不讲解FreeMarkerView视图的相关配置,其配置基本由FreeMarkerViewResolver实现,具体可参考>>>SpringMVC源码情操陶冶-ViewRe ...

  7. SpringMVC源码情操陶冶-AnnotationDrivenBeanDefinitionParser注解解析器

    mvc:annotation-driven节点的解析器,是springmvc的核心解析器 官方注释 Open Declaration org.springframework.web.servlet.c ...

  8. SpringMVC源码情操陶冶-AbstractHandlerMethodMapping

    承接前文SpringMVC源码情操陶冶-AbstractHandlerMapping,本文将介绍如何注册HandlerMethod对象作为handler 类结构瞧一瞧 public abstract ...

  9. SpringMVC源码情操陶冶-AbstractUrlHandlerMapping

    承接前文SpringMVC源码情操陶冶-AbstractHandlerMapping,前文主要讲解了如何获取handler处理对象,本文将针对beanName注册为handler对象作下解析 Abst ...

随机推荐

  1. noip级别数论?

    TAT快noip了才开始去接触数论(真心不敢学..)这里做一下整理吧(都是些定义之类的东西= =) 欧几里德:gcd(a,b)=gcd(b,a%b);具体证明见百科? 扩展欧几里德: 求a*x+b*y ...

  2. 2017ecjtu-summer training # 11 POJ 2492

    A Bug's Life Time Limit: 10000MS   Memory Limit: 65536K Total Submissions: 38280   Accepted: 12452 D ...

  3. Ping pong(树状数组求序列中比某个位置上的数小的数字个数)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2492 Ping pong Time Limit: 2000/1000 MS (Java/Others) ...

  4. Linux shell编程命令-Linux基础环境命令学习笔记

    1.正则表达式 1)^开始 *前一个字符重复0次以上 + 1次以上 ? 0次或者1次 . 一个任意字符(.*连用) {m,n} m到n次 [0-9][a-z] 任意数字或字母 $结束字符 2)sed和 ...

  5. SQL强化(三) 自定义函数

    ---恢复内容开始--- Oracle中我们可以通过自定义函数去做一些逻辑判断,这样可以减少查询语句,提高开发效率 create  -- 创建自定义函数 or replace -- 有同名函数就替换, ...

  6. CSS中的定位与浮动

    CSS中的定位与浮动 本文主要讲述CSS中的三种定位样式static.relative和absolute的区别以及浮动元素的特征. 定位样式 CSS中定位样式position的取值有三个,默认值:st ...

  7. vue-router自动判断左右翻页转场动画

    前段时间做了一个移动端spa项目,技术基于 :vue + vue-router + vuex + mint-ui 因为使用了vue-cli脚手架的webpack模版,所有页面都以.vue为后缀的文件作 ...

  8. 解决DEDECMS Call to undefined function dede_htmlspecialchars()

    作者:DEDECMS建站网 关注: 3610 时间:2015-11-18 16:39 内容详情 以下内容您可能感兴趣: 织梦官方在2015年6月18日更新了织梦5.7,为了兼容php5.4+,修改了/ ...

  9. [机器学习]模型评价参数,准确率,召回率,F1-score

    很久很久以前,我还是有个建筑梦的大二少年,有一天,讲图的老师看了眼我的设计图,说:"我觉得你这个设计做得很紧张".当时我就崩溃,对紧张不紧张这样的评价标准理解无能.多年后我终于明白 ...

  10. Node.js进阶:5分钟入门非对称加密方法

    前言 刚回答了SegmentFault上一个兄弟提的问题<非对称解密出错>.这个属于Node.js在安全上的应用,遇到同样问题的人应该不少,基于回答的问题,这里简单总结下. 非对称加密的理 ...