首页 / 操作系统 / Linux / 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 StopWatchHandlerInterceptorextends HandlerInterceptorAdapter{private static Logger logger = Logger.getLogger(StopWatchHandlerInterceptor.class);private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {long startTime = System.currentTimeMillis();startTimeThreadLocal.set(startTime);return true;}@Overridepublic 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+MyBatis集成配置 http://www.linuxidc.com/Linux/2016-09/135212.htmSpringMVC总结篇 http://www.linuxidc.com/Linux/2016-06/132659.htmSpring+SpringMVC企业快速开发架构搭建 http://www.linuxidc.com/Linux/2015-09/122942.htmSpringMVC的乱码处理 http://www.linuxidc.com/Linux/2015-07/120542.htmSpring MVC+Spring3+Hibernate4开发环境搭建 http://www.linuxidc.com/Linux/2013-07/87119.htmSpring MVC整合Freemarker基于注解方式 http://www.linuxidc.com/Linux/2013-02/79660.htm基于注解的Spring MVC简单介绍 http://www.linuxidc.com/Linux/2012-02/54896.htmSpringMVC详细示例实战教程 http://www.linuxidc.com/Linux/2015-06/118461.htmSpring MVC 框架搭建及详解 http://www.linuxidc.com/Linux/2012-01/52740.htmSpringMVC 异常处理 http://www.linuxidc.com/Linux/2015-06/119049.htmSpringMVC框架入门配置 IDEA下搭建Maven项目 http://www.linuxidc.com/Linux/2016-09/134918.htm本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-10/135876.htm