一個東西用久了,自然就會從僅使用的層面上升到探究其原理的層面,在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+MyBatis集成配置 http://www.linuxidc.com/Linux/2016-09/135212.htm
SpringMVC總結篇 http://www.linuxidc.com/Linux/2016-06/132659.htm
Spring+SpringMVC企業快速開發架構搭建 http://www.linuxidc.com/Linux/2015-09/122942.htm
SpringMVC的亂碼處理 http://www.linuxidc.com/Linux/2015-07/120542.htm
Spring MVC+Spring3+Hibernate4開發環境搭建 http://www.linuxidc.com/Linux/2013-07/87119.htm
Spring MVC整合Freemarker基於注解方式 http://www.linuxidc.com/Linux/2013-02/79660.htm
基於注解的Spring MVC簡單介紹 http://www.linuxidc.com/Linux/2012-02/54896.htm
SpringMVC詳細示例實戰教程 http://www.linuxidc.com/Linux/2015-06/118461.htm
Spring MVC 框架搭建及詳解 http://www.linuxidc.com/Linux/2012-01/52740.htm
SpringMVC 異常處理 http://www.linuxidc.com/Linux/2015-06/119049.htm
SpringMVC框架入門配置 IDEA下搭建Maven項目 http://www.linuxidc.com/Linux/2016-09/134918.htm