spring中的攔截器(HandlerInterceptor+MethodInterceptor) - niceyoo - 博客園
https://www.cnblogs.com/niceyoo/p/8735637.html
spring中的攔截器(HandlerInterceptor+MethodInterceptor)
1. 過濾器跟攔截器的區別
在說攔截器之前,不得不說一下過濾器,有時候往往被這兩個詞搞的頭大。
其實我們最先接觸的就是過濾器,還記得web.xml中配置的<filter>嗎~
你應該知道spring mvc的攔截器是隻攔截controller而不攔截jsp,html 頁面文件的,如果想要攔截那怎麼辦?
這就用到過濾器filter了,filter是在servlet前執行的,你也可以理解成過濾器中包含攔截器,一個請求過來 ,先進行過濾器處理,看程序是否受理該請求 。 過濾器放過後 , 程序中的攔截器進行處理 。
(1)過濾器(Filter):當你有一堆東西的時候,你只希望選擇符合你要求的某一些東西。定義這些要求的工具,就是過濾器。(理解:就是一堆字母中取一個B)
(2)攔截器(Interceptor):在一個流程正在進行的時候,你希望干預它的進展,甚至終止它進行,這是攔截器做的事情。(理解:就是一堆字母中,干預他,通過驗證的少點,順便乾點別的東西)。
2. spring中的攔截器
在web開發中,攔截器是經常用到的功能。它可以幫我們驗證是否登陸、預先設置數據以及統計方法的執行效率等等。
今天就來詳細的談一下spring中的攔截器。spring中攔截器主要分兩種,一個是HandlerInterceptor,一個是MethodInterceptor。
2.1 HandlerInterceptor攔截器
HandlerInterceptor是springMVC項目中的攔截器,它攔截的目標是請求的地址,比MethodInterceptor先執行。
實現一個HandlerInterceptor攔截器可以直接實現HandlerInterceptor接口,也可以繼承HandlerInterceptorAdapter類。
這兩種方法殊途同歸,其實HandlerInterceptorAdapter也就是聲明瞭HandlerInterceptor接口中所有方法的默認實現,而我們在繼承他之後只需要重寫必要的方法。
下面就是HandlerInterceptorAdapter的代碼,可以看到一個方法只是默認返回true,另外兩個是空方法:
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor { /** * This implementation always returns <code>true</code>. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * This implementation is empty. */ public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * This implementation is empty. */ public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
這三個方法都是幹什麼的,有什麼作用,什麼時候調用,不同的攔截器之間是怎樣的調用順序呢?
先補一張圖:
這還得參考一下DispatcherServlet的doDispatch方法:
View Code
代碼有點長,但是它封裝了springMVC處理請求的整個過程。首先根據請求找到對應的HandlerExecutionChain,它包含了處理請求的handler和所有的HandlerInterceptor攔截器;然後在調用hander之前分別調用每個HandlerInterceptor攔截器的preHandle方法,若有一個攔截器返回false,則會調用triggerAfterCompletion方法,並且立即返回不再往下執行;若所有的攔截器全部返回true並且沒有出現異常,則調用handler返回ModelAndView對象;再然後分別調用每個攔截器的postHandle方法;最後,即使是之前的步驟拋出了異常,也會執行triggerAfterCompletion方法。關於攔截器的處理到此爲止,接下來看看triggerAfterCompletion做了什麼:
View Code
triggerAfterCompletion做的事情就是從當前的攔截器開始逆向調用每個攔截器的afterCompletion方法,並且捕獲它的異常,也就是說每個攔截器的afterCompletion方法都會調用。
根據以上的代碼,分析一下不同攔截器及其方法的執行順序。假設有5個攔截器編號分別爲12345,若一切正常則方法的執行順序是12345的preHandle,54321的postHandle,54321的afterCompletion。若編號3的攔截器的preHandle方法返回false或者拋出了異常,接下來會執行的是21的afterCompletion方法。這裏要注意的地方是,我們在寫一個攔截器的時候要謹慎的處理preHandle中的異常,因爲這裏一旦有異常拋出就不會再受到這個攔截器的控制。12345的preHandle的方法執行過之後,若handler出現了異常或者某個攔截器的postHandle方法出現了異常,則接下來都會執行54321的afterCompletion方法,因爲只要12345的preHandle方法執行完,當前攔截器的攔截器就會記錄成編號5的攔截器,而afterCompletion總是從當前的攔截器逆向的向前執行。
2.2 MethodInterceptor攔截器
MethodInterceptor是AOP項目中的攔截器,它攔截的目標是方法,即使不是controller中的方法。實現MethodInterceptor攔截器大致也分爲兩種,一種是實現MethodInterceptor接口,另一種利用AspectJ的註解或配置。
下面是第一種方法的示例
public class MethodInvokeInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("before method invoke"); Object object = methodInvocation.proceed(); System.out.println("after method invoke"); return object; } }
下面是基於註解的AspectJ方式
@Component @Aspect public class AutoAspectJInterceptor { @Around("execution (* com.test.controller..*.*(..))") public Object around(ProceedingJoinPoint point) throws Throwable{ System.out.println("AutoAspectJInterceptor begin around"); Object object = point.proceed(); System.out.println("AutoAspectJInterceptor end around"); return object; } }
下面是一個用於支持AspectJ方式攔截的普通的bean,當然你也可以在配置文件中聲明這個bean
@Component public class AspectJInterceptor { public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("AspectJInterceptor around before"); Object object = proceedingJoinPoint.proceed(); System.out.println("AspectJInterceptor around after"); return object; } }
當然,這一切都離不開配置,具體看配置中的註釋
<!-- 自定義攔截器 ,先過mvc:interceptors--> <bean id="methodInvokeInterceptor" class="com.test.interceptor.MethodInvokeInterceptor"/> <bean id="aspectInterceptor" class="com.test.interceptor.AspectJInterceptor"/> <aop:config> <!--切入點,controlller --> <aop:pointcut id="pointcut_test" expression="execution(* com.test.controller..*.*(..))" /> <!--在該切入點使用自定義攔截器 ,按照先後順序執行 --> <aop:advisor pointcut-ref="pointcut_test" advice-ref="methodInvokeInterceptor" /> <aop:aspect ref="aspectInterceptor"> <aop:around method="around" pointcut="execution(* com.test.controller..*.*(..))"/> </aop:aspect> </aop:config> <!-- 自動掃描使用了aspectj註解的類 --> <aop:aspectj-autoproxy/>
通過上面的配置三個MethodInterceptor就能正常工作了。其實,這兩種實現方最終...沒錯,還是殊途同歸。
aspectj的攔截器會被解析成AOP中的advice,最終被適配成MethodInterceptor,詳細的過程請參考springAOP的實現。
3. 實例選擇攔截器
項目中採用Interceptor來過濾URL來決定哪些可以在不登錄的情況下訪問,哪些必須要登錄纔可以訪問;
3.1 HandlerInterceptor方式
public class SessionTimeoutInterceptor implements HandlerInterceptor { ......... }
此時需要在servlet.xml中配置<mvc:interceptor>
3.2 MethodInterceptor註解Aspect方式
@Component @Aspect public void class BindingResultAop{ ........ }
同時在servlet.xml中配置
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
經測試發現,interceptor先於AOP執行。
4. 談一談區別
上面的兩種攔截器都能起到攔截的效果,但是他們攔截的目標不一樣,實現的機制不同,所以有的時候適用不同的場景。
HandlerInterceptoer攔截的是請求地址,所以針對請求地址做一些驗證、預處理等操作比較合適。當你需要統計請求的響應時間時MethodInterceptor將不太容易做到,因爲它可能跨越很多方法或者只涉及到已經定義好的方法中一部分代碼。MethodInterceptor利用的是AOP的實現機制,在本文中只說明了使用方式,關於原理和機制方面介紹的比較少,因爲要說清楚這些需要講出AOP的相當一部分內容。在對一些普通的方法上的攔截HandlerInterceptoer就無能爲力了,這時候只能利用AOP的MethodInterceptor。
另外,還有一個跟攔截器類似的東西----Filter。Filter是Servlet規範規定的,不屬於spring框架,也是用於請求的攔截。但是它適合更粗粒度的攔截,在請求前後做一些編解碼處理、日誌記錄等。而攔截器則可以提供更細粒度的,更加靈活的,針對某些請求、某些方法的組合的解決方案。
另外的另外,用過人人網的ROSE框架的人都會非常喜歡它的攔截器功能。因爲它實現了全註解的方式,只要在類的名字上加上攔截器的註解即表示這是一個攔截器。而使用這個攔截器的方法或者controller也只需在方法或controller的上面加上這個攔截器的註解。其實這是一個關注點的轉變,spring的切面控制在配置文件中,配置文件關注哪些地方需要攔截。而在ROSE中,則是在需要攔截的地方關注我要被誰攔截。