本文作者:鐘昕靈,叩丁狼高級(jí)講師。原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處。
前言
Spring MVC屬于SpringFrameWork的后續(xù)產(chǎn)品,已經(jīng)融合在Spring Web Flow里面。Spring 框架提供了構(gòu)建 Web 應(yīng)用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構(gòu),從而在使用Spring進(jìn)行WEB開發(fā)時(shí),可以選擇使用Spring的SpringMVC框架或集成其他MVC開發(fā)框架,如Struts1(現(xiàn)在一般不用),Struts2(一般老項(xiàng)目使用)等。
SpringMVC中的Interceptor攔截器用于攔截Controller層接口,表現(xiàn)形式有點(diǎn)像Spring的AOP,但是AOP是針對(duì)單一的方法。Interceptor是針對(duì)Controller接口以及可以處理request和response對(duì)象。
下面,我們來看看SpringMVC中攔截器的使用及實(shí)現(xiàn)
HandlerInterceptor接口的定義
在該接口中,定義了一下三個(gè)方法
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;
}
preHandle:
在訪問到達(dá)Controller之前執(zhí)行,如果需要對(duì)請(qǐng)求做預(yù)處理,可以選擇在該方法中完成
返回值為true:繼續(xù)執(zhí)行后面的攔截器或者Controller
返回值為false:不再執(zhí)行后面的攔截器和Controller,并調(diào)用返回true的攔截器的afterCompletion方法postHandle:
在執(zhí)行完Controller方法之后,渲染視圖之前執(zhí)行,如果需要對(duì)響應(yīng)相關(guān)的數(shù)據(jù)進(jìn)行處理,可以選擇在該方法中完成afterCompletion:
調(diào)用完Controller接口,渲染View頁面后調(diào)用。返回true的攔截器都會(huì)調(diào)用該攔截器的afterCompletion方法,順序相反。
自定義攔截器和使用
自定義一個(gè)我們自己的攔截器非常簡(jiǎn)單,定義一個(gè)類,實(shí)現(xiàn)上面的接口,然后覆寫對(duì)應(yīng)的方法即可
當(dāng)然,在實(shí)際開發(fā)中,我們一般選擇繼承該接口的實(shí)現(xiàn)類HandlerInterceptorAdapter來實(shí)現(xiàn)攔截器的定義,如下:
public class CheckLoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1:獲取登錄憑證
Employee emp = UserContext.getCurrentUser();
if(emp == null){
//沒有登錄
response.sendRedirect("/login.html");
return false; //終止請(qǐng)求繼續(xù)往下執(zhí)行
}
return true; //放行
}
}
上面,我們定義了一個(gè)檢查用戶是否登錄的攔截器,如果沒有登錄,跳轉(zhuǎn)到登錄頁面,反之,放行繼續(xù)訪問目標(biāo)資源
要讓我們的攔截器被框架得知并管理,我們還需要在配置文件中做如下配置來注冊(cè)攔截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login.do"/>
<mvc:exclude-mapping path="/login.html"/>
<bean class="cn.wolfcode.rbac.web.interceptor.CheckLoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
如此,攔截器就能夠在項(xiàng)目中部署起來
當(dāng)用戶發(fā)起請(qǐng)求相應(yīng)資源的時(shí)候,會(huì)首先經(jīng)過該攔截器的處理,防止用戶在沒有登錄的情況下直接訪問項(xiàng)目中的核心資源
原理解析
可以看出,攔截器在SpringMVC框架中實(shí)現(xiàn)是非常簡(jiǎn)單的,但是,大家一定要清楚一個(gè)道理
當(dāng)你覺得很輕松的時(shí)候,是有另外一些人替你負(fù)重前行
這個(gè)時(shí)候,是誰在為我們負(fù)重前行呢?當(dāng)然是我們使用的SpringMVC框架了!
那么,框架這個(gè)時(shí)候都為我們做了哪些事情呢?請(qǐng)往下看:
SpringMVC框架的入口是一個(gè)使用Servlet實(shí)現(xiàn)的前端控制器:
- DispatcherServlet
我們的每次請(qǐng)求都會(huì)先經(jīng)過這個(gè)入口的處理才能到達(dá)目標(biāo)資源,所以,來看看這里都做了哪些事吧
該類中,最重要的一個(gè)方法是doDispatch,在這個(gè)方法中,完成了整個(gè)執(zhí)行流程的任務(wù)分配
下面是該方法中的部分核心代碼:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//返回 HandlerExecutionChain 其中包含了攔截器隊(duì)列
mappedHandler = this.getHandler(processedRequest);
if(mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//獲取到適合處理當(dāng)前請(qǐng)求的適配器,最終用來調(diào)用Controller中的方法
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//調(diào)用攔截器鏈中所有攔截器的preHandle方法
if(!mappedHandler.applyPreHandle(processedRequest, response)) {
//如果有攔截器的preHandle方法返回值為false,則結(jié)束該方法的執(zhí)行
return;
}
//調(diào)用請(qǐng)求的Controller中的方法,獲取到ModelAndView對(duì)象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//調(diào)用攔截器鏈中所有攔截器的postHandle方法,和執(zhí)行preHandle方法的順序相反
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var19) {
dispatchException = var19;
}
//處理視圖渲染
this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception var20) {
//如果在執(zhí)行過程中有異常,執(zhí)行后續(xù)的收尾工作,執(zhí)行對(duì)應(yīng)攔截器中的afterCompletion方法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
}
}
mappedHandler = this.getHandler(processedRequest);
返回 HandlerExecutionChain 其中包含了攔截器隊(duì)列HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
獲取到適合處理當(dāng)前請(qǐng)求的適配器,最終用來調(diào)用Controller中的方法mappedHandler.applyPreHandle(processedRequest, response)
調(diào)用攔截器鏈中所有攔截器的preHandle方法mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
調(diào)用請(qǐng)求的Controller中的方法,獲取到ModelAndView對(duì)象mappedHandler.applyPostHandle(processedRequest, response, mv);
調(diào)用攔截器鏈中所有攔截器的postHandle方法,和執(zhí)行preHandle方法的順序相反this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
處理結(jié)果視圖的渲染,簡(jiǎn)單說就是頁面的跳轉(zhuǎn)問題this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
如果在執(zhí)行過程中有異常,執(zhí)行后續(xù)的收尾工作,執(zhí)行對(duì)應(yīng)攔截器中的afterCompletion方法
通過上面對(duì)DispatcherServlet中核心代碼的分析,相信大家對(duì)攔截器的執(zhí)行流程有了大致的理解
下面我們?cè)賹?duì)這個(gè)過程中的細(xì)節(jié)繼續(xù)進(jìn)行分析:
-
獲取攔截器
DispatcherServlet:protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { HandlerExecutionChain handler; handler = hm.getHandler(request); return handler; }AbstractHandlerMapping:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = this.getHandlerInternal(request); if(handler == null) { handler = this.getDefaultHandler(); } if(handler == null) { return null; } else { HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request); return executionChain; } }AbstractHandlerMapping:
遍歷所有的攔截器, 把所有匹配當(dāng)前請(qǐng)求的所有攔截添加到攔截器隊(duì)列中protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = handler instanceof HandlerExecutionChain?(HandlerExecutionChain)handler:new HandlerExecutionChain(handler); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); Iterator var5 = this.adaptedInterceptors.iterator(); while(var5.hasNext()) { HandlerInterceptor interceptor = (HandlerInterceptor)var5.next(); if(interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor; if(mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }MappedInterceptor:
<mvc:exclude-mapping path="/login.html"/> 如果請(qǐng)求資源路徑為 /login.html 則排除當(dāng)前攔截器
<mvc:mapping path="/"/>如果請(qǐng)求資源路徑為 不在exclude-mapping中,且能夠匹配 / 路徑, 則添加到攔截器隊(duì)列public boolean matches(String lookupPath, PathMatcher pathMatcher) { PathMatcher pathMatcherToUse = this.pathMatcher != null?this.pathMatcher:pathMatcher; String[] var4; int var5; int var6; String pattern; if(this.excludePatterns != null) { var4 = this.excludePatterns; var5 = var4.length; for(var6 = 0; var6 < var5; ++var6) { pattern = var4[var6]; if(pathMatcherToUse.match(pattern, lookupPath)) { return false; } } } if(this.includePatterns == null) { return true; } else { var4 = this.includePatterns; var5 = var4.length; for(var6 = 0; var6 < var5; ++var6) { pattern = var4[var6]; if(pathMatcherToUse.match(pattern, lookupPath)) { return true; } } return false; } } -
處理攔截器
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]; //調(diào)用攔截器中的preHandle方法 if(!interceptor.preHandle(request, response, this.handler)) { // 如果preHandler方法返回false,則觸發(fā)afterCompletion方法的執(zhí)行 this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } return true; }void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[i]; //調(diào)用攔截器中的postHandle方法 interceptor.postHandle(request, response, this.handler, mv); } } }當(dāng)前攔截器中preHandle方法如果返回true,則該方法會(huì)在下面幾種情況的時(shí)候會(huì)執(zhí)行
①Controller正常執(zhí)行,視圖渲染后
②程序有異常的時(shí)候
③在任何攔截器preHandle方法返回false的時(shí)候
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = this.interceptorIndex; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
try {
//調(diào)用攔截器中的afterCompletion方法
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
}
可以看到,SpringMVC在執(zhí)行這一系列的處理的時(shí)候,做了很多的細(xì)節(jié)處理,但是,在我們看源碼的時(shí)候最好能夠排除和功能無關(guān)的代碼,這樣有利于我們理解整個(gè)執(zhí)行流程
所以,在上面的代碼中,我僅僅將這個(gè)過程中比較重要的代碼貼了出來, 不是完整的代碼,如果要看完成的代碼,請(qǐng)自行參考框架的源碼學(xué)習(xí),謝謝
想獲取更多技術(shù)視頻,請(qǐng)前往叩丁狼官網(wǎng):http://www.wolfcode.cn/openClassWeb_listDetail.html