攔截器的實現原理很簡單,就是動態代理,實現AOP機制。當外部調用被攔截bean的攔截方法時,可以選擇在攔截之前或者之后等條件執行攔截方法之外的邏輯,比如特殊權限驗證,參數修正等操作。
但是最近在項目中要在一個事務中跨數據源進行操作。數據庫跨源就不在這里說了,可以自行百度。
這里出現的問題就是,如果我們要攔截某個類的多個方法,且在該類的方法中使用this調用要攔截的方法時會導致攔截失敗。
這樣說有點抽象,看一個代碼:
package com.intsmaze.before; public class AopDemo { /** * controller層調用的邏輯service方法,該方法內部調用分別訪問不同數據庫的service方法 */ public void findInforAll() { this.findDataBaseA(); this.findDataBaseB(); } /** * 這里我們就查詢A數據庫的某張表好了 */ public void findDataBaseA() { } /** * 這里我們就查詢B數據庫的某張表好了 */ public void findDataBaseB() { } }
然后我們使用spring的aop寫一個進行切入的類,該類主要就是在執行findDataBaseA和findDataBaseB前執行某些代碼來告知后面的查詢操作查詢數據庫的地址。
@Aspect public class AuthAspect { // 匹配com.intsmaze.before包下AopDemo 類的 // 所有方法的執行作為切入點 @Before("execution(* com.intsmaze.before.AopDemo .*(..))") public void authority() { System.out.println("模擬執行權限檢查"); } }
接下來我就以比喻進行說明了,明白我的比喻的前提你要知道動態代理是什么,打個不恰當的比方吧,就是動態生成一個新的**類**(注意不是對象)。
這里我們可以這樣看當執行findInforAll的時候,AOP監測到這個方法是要被攔截的,於是生成了一個代理類,就是一個新的類。
public class AopDemo+其他名稱 { /** * controller層調用的邏輯service方法,該方法內部調用分別訪問不同數據庫的service方法 */ public void findInforAll() { //--------------------------------------------------- System.out.println("模擬執行權限檢查"); //--------------------------------------------------- this.findDataBaseA(); this.findDataBaseB(); } /** * 這里我們就查詢A數據庫的某張表好了 */ public void findDataBaseA() { } /** * 這里我們就查詢B數據庫的某張表好了 */ public void findDataBaseB() { } }
這里我們可以看到,其實生成一個新的類,該類在findInforAll方法中第一行加上了AOP指定執行的方法。我們系統中調用的就是這個代理類的findInforAll方法,而該方法中的 this.findDataBaseA();this.findDataBaseB();是不會被AOP攔截的,因為AOP攔截的包名和類名很明顯和代理類的不一樣,所以這就是為什么內部調用的方法無法攔截的原因。不知道這樣說,大家懂了沒有。關於如何用JDK寫動態代理,我會在放假時回顧以前筆記,在寫出來。
如何解決呢?我們開始想的時避免AOP切入的類中使用this內部調用,但是發現這樣增加了代碼結構的復雜度,本來只需要一個類,最后要使用兩個類進行管理。太麻煩。
然后考慮項目的進度,就使用把邏輯代碼封裝成工具方法進行調用。
在網上查資料有方法可以解決,沒有測試。
http://blog.csdn.net/quzishen/article/details/5803721下面是主要思路。
在spring的源代碼中通過一個增強對象的檢查,控制了當前的內部調用是否使用代理來執行,這讓人感到無奈。spring的作者們很隱晦的提出避免內部調用的方法。
我們可能會想,在外部調用兩次beanA,第一次調用method1,第二次調用method2,這樣做可以解決問題,但是這樣的直接后果是我們的邏輯代碼將變得紊亂,並非所有的場景下都可以通過這樣的設計來完成。雖然這是spring官方推薦的避免內部調用的idea。
查看了相關資料,得到了一種方法,即在method1的內部,通過直接獲取當前代理對象的方式然后通過代理對象調用method2,這樣觸發攔截。
看看代碼:
public void method1(){ logger.error("1"); // 如果希望調用的內部方法也被攔截,那么必須用過上下文獲取代理對象執行調用,而不能直接內部調用,否則無法攔截 if(null != AopContext.currentProxy()){ ((NorQuickNewsDAO)AopContext.currentProxy()).method2(); }else{ method2(); } } public void method2(){ logger.error("2"); }
我們顯示的調用了AopContext來獲取當前代理對象,然后調用其方法,這樣做還必須的一個步驟是將當前的代理暴露給線程使用,在配置文件中需要配置一個參數:
<property name="exposeProxy"> <value>true</value> </property>
它是ProxyConfig的一個參數,默認是false,如果不設置這個參數,那么上述java代碼將無法獲取當前線程中的代理對象。
這種方法可以成功觸發攔截,但是也帶來了其他問題,比如代碼的織入,我們的代碼將變得復雜而且晦澀,而且嚴格要求系統針對於當前的bean必須配置攔截器,否則會因為找不到攔截器而拋出異常。
這樣做有什么負面影響?對事務的影響,對安全的影響,現在不得而知,還需要逐步去測試以嘗試。