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 ...
随机推荐
- Codeforces Round #423 (Div. 2, rated, based on VK Cup Finals)爆零记
昨晚一个瓜皮说今晚有cf,听说是晚间场,我瞅了一眼,娃,VK Cup,上分的好机会,看着比赛时间就有点心酸了,0:35,当时一直在纠结要不要打的问题,当时想着应该不难吧,要不打一下吧,要不还是看看题先 ...
- 几道数位DP
因为这几天写的几道数位DP大多都太水..而且也确实没什么好讲所以就扔到一起了. [hdu4772]Good Numbers 要求统计区间内 各位数之和能被10整除 的数的个数. 练手,f[i][j][ ...
- Codeforces Round #449 (Div. 2)-897A.Scarborough Fair(字符替换水题) 897B.Chtholly's request(处理前一半) 897C.Nephren gives a riddle(递归)
A. Scarborough Fair time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- 根据父节点parentid查询节点信息
---恢复内容开始--- SELECT * from tb3 where pid in(select id from tb1 where parentId ='ce2a98d7a04c4bf6a38 ...
- Spark学习笔记1(初始spark
1.什么是spark? spark是一个基于内存的,分布式的,大数据的计算框架,可以解决各种大数据领域的计算问题,提供了一站式的服务 Spark2009年诞生于伯克利大学的AMPLab实验室 2010 ...
- java实现死锁的demo
死锁 只有当t1线程占用o1且正好也需要o2,t2此时占用o2且正好也需要o1的时候才会出现死锁,(类似于2个人拿着两个筷子吃饭,都是需要对方的一根筷子才能吃) 以下代码t1线程占用o1,并且获取到o ...
- Web Component总结
Web Component 一个Web组件通常由四个部分组成:模板.Shadow DOM.自定义元素与打包,其中Shadow DOM解决了组件在页面中的封装问题 Shadow DOM 有shadow ...
- APP测试时常用adb命令
ADB全称Android Debug Bridge, 是android sdk里的一个工具, 用这个工具可以直接操作管理android模拟器或者真实的andriod设备(手机),故在其实工作可以给我们 ...
- JS_全
<script src="jquery-1.9.1.js" type="text/javascript"></script> <s ...
- 邓_phpcms_二次开发_留言板
================================================================= •在 phpcms/modules 目录下创建文件夹,并将其命名为g ...