springMVC源码分析之拦截器
一个东西用久了,自然就会从仅使用的层面上升到探究其原理的层面,在javaweb中springmvc更是如此,越是优秀的框架,其底层实现代码更是复杂,而在我看来,一个优秀程序猿就相当于一名武林高手,不断进阶武功秘籍,越是高深莫测的功夫,越是要探究其原理,而springmvc就是一本十分深奥的武功秘籍。
说起拦截器,说不得不和过滤器进行对比,在此贴图一张不进行多加解释,简单的来说拦截器能作用于controller层方法实现的前后而过滤器不能。
在这里先列出一个简单的controller层的实现

正常访问之后我们看看控制台

我们都知道DispatcherServlet是所谓前端控制器,是整个Springmvc的入口,但是这个前端控制器里面又有许多门,我们都看过箱子里面装着又一个箱子,跟这种感觉差不多。
DispatcherServlet里面执行处理入口的方法是doService,先看源码
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if(this.logger.isDebugEnabled()) {
String attributesSnapshot = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult()?" resumed":"";
this.logger.debug("DispatcherServlet with name \'" + this.getServletName() + "\'" + attributesSnapshot + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
HashMap attributesSnapshot1 = null;
if(WebUtils.isIncludeRequest(request)) {
attributesSnapshot1 = new HashMap();
Enumeration inputFlashMap = request.getAttributeNames();
label108:
while(true) {
String attrName;
do {
if(!inputFlashMap.hasMoreElements()) {
break label108;
}
attrName = (String)inputFlashMap.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
attributesSnapshot1.put(attrName, request.getAttribute(attrName));
}
}
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
FlashMap inputFlashMap1 = this.flashMapManager.retrieveAndUpdate(request, response);
if(inputFlashMap1 != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap1));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
this.doDispatch(request, response);
} finally {
if(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot1 != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot1);
}
}
}
由于主要是先分析拦截器,doservice的其他部分就先不解释,先看一段代码
this.logger.debug("DispatcherServlet with name \'" + this.getServletName() + "\'" + attributesSnapshot + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
这段跟我贴出的控制台信息截图的第一段信息是不是很相似,由此可以证明,doService的确是执行处理方法的入口。但是doService并没有直接进行处理,而是交给了doDispatch进行具体的处理。下面的doDispatch的源码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView err = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if(mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ex = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if(isGet || "HEAD".equals(method)) {
long lastModified = ex.getLastModified(request, mappedHandler.getHandler());
if(this.logger.isDebugEnabled()) {
this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if(!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
err = ex.handle(processedRequest, response, mappedHandler.getHandler());
if(asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(request, err);
mappedHandler.applyPostHandle(processedRequest, response, err);
} catch (Exception var19) {
dispatchException = var19;
}
this.processDispatchResult(processedRequest, response, mappedHandler, err, dispatchException);
} catch (Exception var20) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
} catch (Error var21) {
this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21);
}
} finally {
if(asyncManager.isConcurrentHandlingStarted()) {
if(mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if(multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
那么问题来了,既然我发出的请求是转发到doDispatch进行具体请求,那么请求和Controller层之间是怎么联系上的,我们再来看看控制层的信息

第一段的信息是查找运用在请求的url /a上的方法,第二段即是找到方法并返回,第三段则是找到了Controller,而这一整个过程都是由HandlerMapping进行工作的。没错,虽然拦截器是作用于控制层前后,但我们确实是先找控制层,拦截器再起作用。
然后找到代码中我们要的信息
if(!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
也是说这部分封装的是控制层执行之前的方法,我们打开这个方法可以看到
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
这个方法先是判断拦截器是否为空,然后用for循环对每个拦截器Intercepter使用preHandler方法,我们先留意到里面一句代码
HandlerInterceptor interceptor = interceptors[i];
每个拦截器 Intercepter 都必须继承或者实现 HandlerInterceptor,所以这样声明类型是运用到多态。
我们打开preHandle方法可以看到
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}
自然而然出现的HandlerIntercetor,这是用到多态的只是,超类可以调用子类方法,从而实现解耦以及拓展性。
处理完preHandle后,就到了执行控制层的方法,处理完之后先进行对view的处理,当view为空时,设置默认view,然后就执行控制层执行之后的方法,也就是postHandle
this.applyDefaultViewName(request, err);
mappedHandler.applyPostHandle(processedRequest, response, err);
接着调用postHandle的调用过程也preHandle方法相似,最后使用processDispatchResult方法处理前面返回的结果,其中包括处理异常,渲染页面,触发Interceptor的afterCompletion方法。
自此我们已经彻底分析完源码当中关于拦截器的代码,在现实当中,经常用的更是自定义拦截器,主要作于于:
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。
…………本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。
自定义拦截器需要继承或者实现HandlerInterceptorAdapter类,在此贴出记录时间的代码
public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter{
private static Logger logger = Logger.getLogger(StopWatchHandlerInterceptor.class);
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long endTime = System.currentTimeMillis();
long startTime = startTimeThreadLocal.get();
String uri = request.getRequestURI();
logger.info("handle uri:" + uri + " for " + (endTime - startTime) + " ms.");
}
}
在dispacher的配置文件加上
<!-- 全局拦截器 -->
<mvc:interceptors>
<!--拦截所有请求-->
<bean class="scau.zzf.interceptor.interceptor.StopWatchHandlerInterceptor"/>
<mvc:interceptors/>
springMVC源码分析之拦截器的更多相关文章
- springMVC源码分析--HandlerInterceptor拦截器调用过程(二)
在上一篇博客springMVC源码分析--HandlerInterceptor拦截器(一)中我们介绍了HandlerInterceptor拦截器相关的内容,了解到了HandlerInterceptor ...
- springMVC源码分析--HandlerInterceptor拦截器(一)
对SpringMVC有所了解的人肯定接触过HandlerInterceptor拦截器,HandlerInterceptor接口给我们提供了3个方法: (1)preHandle: 在执行controll ...
- SpringMVC源码阅读:拦截器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- springMVC源码分析--拦截器HandlerExecutionChain(三)
上一篇博客springMVC源码分析--HandlerInterceptor拦截器调用过程(二)中我们介绍了HandlerInterceptor的执行调用地方,最终HandlerInterceptor ...
- springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)
在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...
- springMVC源码分析--RequestParamMethodArgumentResolver参数解析器(三)
之前两篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)和springMVC源码解析--HandlerMethodArgumentResol ...
- 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解
从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...
- springMVC源码分析--AbstractUrlHandlerMapping(三)
上一篇博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMapping了,接下来我们介绍其子类AbstractUrlHand ...
- springMVC源码分析--AbstractHandlerMapping(二)
上一篇博客springMVC源码分析--HandlerMapping(一)中我们简单的介绍了HandlerMapping,接下来我们介绍一下它的抽象实现类AbstractHandlerMapping
随机推荐
- Django抛错不存在(DoesNotExist)
from django.core.exceptions import ObjectDoesNotExist try: disabledusers.objects.get(sAMAccountName= ...
- Docker实践(5)—资源隔离
Docker使用cgroup实现CPU,内存和磁盘IO等系统资源的限制. CPU Docker现在有2个与CPU资源相关的参数,-c可以指定CPU的占比,--cpuset可以绑定CPU.例如,指定容器 ...
- python3 crypto winrandom import error
早就听说3的包很成熟了,自从从2.7过渡上来后还是碰到各种不适应,可以想象更早的时候问题该要多么多,特别一些必备库经典库如果没有跟进得多痛苦. [code lang="python" ...
- 【Vegas原创】RHEL6多界面切换方法
CTRL+ALT+Fn(n=1-6) F1,是图形化界面 F2-F6,是字符型界面. 每个界面可以干不同的事情,很牛叉.
- Android开发(二十九)——layout_weight的含义
首先声明只有在Linearlayout中,该属性才有效.之所以android:layout_weight会引起争议,是因为在设置该属性的同时,设置android:layout_width为wrap_c ...
- 倒计时,js
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- 【国内独家首发】iPhone4 iOS7不完美越狱教程新鲜出炉
准备工作: 1.Mac (OSX 10.8以上系统): 2.iPhone 4 (iPhone3, 1 iOS 7.0.2): 3.iPhone 4数据线一根: 4.下载越狱工具包:ios7_jb_ki ...
- 几组User-Agent
Your User Agent String is: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.19 (KHTML, like Gecko) Ubun ...
- struts2:struts.xml配置文件详解
1. 几个重要的元素 1.1 package元素 package元素用来配置包.在Struts2框架中,包是一个独立的单位,通过name属性来唯一标识包.还可以通过extends属性让一个包继承另一个 ...
- iOS touch事件单击双击区分响应
如果您的 iPhone 应用里有个 view,既有单击操作又有双击操作.用户双击 view 时,总是先执行一遍单击的操作再执行双击的操作.所以直接判断时就会发现不能直接进入双击操作.下面是区分 tou ...