一個東西用久了,天然就會從僅使用的層面上升到探究其原理的層面,在javaweb中springmvc更是如此,越是優秀的框架,其底層實現代碼更是複雜,而在我看來,一個優秀程序猿就至關於一名武林高手,不斷進階武功祕籍,越是高深莫測的功夫,越是要探究其原理,而springmvc就是一本十分深奧的武功祕籍。前端
提及攔截器,說不得不和過濾器進行對比,在此貼圖一張不進行多加解釋,簡單的來講攔截器能做用於controller層方法實現的先後而過濾器不能。java
在這裏先列出一個簡單的controller層的實現web
正常訪問以後咱們看看控制檯spring
咱們都知道DispatcherServlet是所謂前端控制器,是整個Springmvc的入口,可是這個前端控制器裏面又有許多門,咱們都看過箱子裏面裝着又一個箱子,跟這種感受差很少。apache
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的其餘部分就先不解釋,先看一段代碼cookie
this.logger.debug("DispatcherServlet with name \'" + this.getServletName() + "\'" + attributesSnapshot + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
這段跟我貼出的控制檯信息截圖的第一段信息是否是很類似,由此能夠證實,doService的確是執行處理方法的入口。可是doService並無直接進行處理,而是交給了doDispatch進行具體的處理。下面的doDispatch的源碼mvc
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層之間是怎麼聯繫上的,咱們再來看看控制層的信息app
第一段的信息是查找運用在請求的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方法。
自此咱們已經完全分析完源碼當中關於攔截器的代碼,在現實當中,常常用的更是自定義攔截器,主要做于于:
一、日誌記錄:
記錄請求信息的日誌,以便進行信息監控、信息統計、計算PV(Page View)等。
二、權限檢查:
如登陸檢測,進入處理器檢測檢測是否登陸,若是沒有直接返回到登陸頁面;
三、性能監控:
有時候系統在某段時間莫名其妙的慢,能夠經過攔截器在進入處理器以前記錄開始時間,在處理完後記錄結束時間,從而獲得該請求的處理時間(若是有反向代理,如apache能夠自動記錄);
四、通用行爲:
讀取cookie獲得用戶信息並將用戶對象放入請求,從而方便後續流程使用,還有如提取Locale、Theme信息等,只要是多個處理器都須要的便可使用攔截器實現。
五、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/>