一、Spring的AOP的動態代理實現機制有兩種,分別是:
1、JDK動態代理:
具體實現原理:
1、通過實現InvocationHandler接口創建自己的調用處理器
2、通過為Proxy類指定ClassLoader對象和一組interface來創建動態代理
3、通過反射機制獲取動態代理類的構造函數,其唯一參數類型就是調用處理器接口類型
4、通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數參入
JDK動態代理是面向接口的代理模式,如果被代理目標沒有接口那么Spring也無能為力,
Spring通過java的反射機制生產被代理接口的新的匿名實現類,重寫了其中AOP的增強方法。
2、CGLib動態代理
CGLib是一個強大、高性能的Code生產類庫,可以實現運行期動態擴展java類,Spring在運行期間通過 CGlib繼承要被動態代理的類,重寫父類的方法,實現AOP面向切面編程呢。
兩者對比:
JDK動態代理是面向接口,在創建代理實現類時比CGLib要快,創建代理速度快。
CGLib動態代理是通過字節碼底層繼承要代理類來實現(如果被代理類被final關鍵字所修飾,那么抱歉會失敗),在創建代理這一塊沒有JDK動態代理快,但是運行速度比JDK動態代理要快。
使用注意:
如果要被代理的對象是個實現類,那么Spring會使用JDK動態代理來完成操作(Spirng默認采用JDK動態代理實現機制)
如果要被代理的對象不是個實現類那么,Spring會強制使用CGLib來實現動態代理。
二、同一對象內嵌套方法注解不生效問題
在代理對象方法中,無論如何添加橫切邏輯,不管添加多少橫切邏輯,最終還是需要調用目標對象上的同一方法來執行最初所定義的方法邏輯。
如果目標對象中原始方法調用依賴於其他對象,我們可以為目標對象注入所需依賴對象的代理,並且可以保證想用的JoinPoint被攔截並織入橫切邏輯。而一旦目標對象中的原始方法直接調用自身方法的時候,也就是說依賴於自身定義的其他方法時,就會出現如下圖所示問題:
package demo.interf.impl; import demo.interf.ICustomerService; public class CustomerServiceProxy implements ICustomerService { private ICustomerService customerService; public void setCustomerService(ICustomerService customerService) { this.customerService = customerService; } public void doSomething1() { doBefore(); customerService.doSomething1(); doAfter(); } public void doSomething2() { doBefore(); customerService.doSomething2(); doAfter(); } private void doBefore() { // 例如,可以在此處開啟事務 System.out.println("do some important things before..."); } private void doAfter() { // 例如,可以在此處提交或回滾事務、釋放資源等等 System.out.println("do some important things after..."); } }
在代理對象的method1()
方法執行經歷了層層攔截器后,最終會將調用轉向目標對象上的method1()
,之后的調用流程全部都是在TargetClassDefinition
中,當method1()
調用method2()
時,它調用的是TargetObject
上的method2()
而不是ProxyObject
上的method2()
。而針對method2()
的橫切邏輯,只織入到了ProxyObject
上的method2()
方法中。所以,在method1()
中調用的method2()
沒有能夠被攔截成功。
解決方案:
1、applicationContext.getBean(XXX).getMethod();
2、((TargetClassDefinition) AopContext.currentProxy()).method2();(需要ProxyConfig或者它相應的子類的exposeProxy屬性設置為true)
jdk是代理接口,私有方法必然不會存在在接口里,所以就不會被攔截到;
cglib是子類,private的方法照樣不會出現在子類里,也不能被攔截。
execution(public * test.aop.ServiceA.*(..))
還有個奇怪的現象,execution里如果不寫權限,那么public protected package的方法都能被攔截到
如果寫了public,那就只攔截public方法這個沒問題,
如果寫了protected,他就什么事情都不做,連protected的方法也不攔截。
分析
private方法 在Spring使用純Spring AOP(只能攔截public/protected/包)都是無法被攔截的 因為子類無法覆蓋;包級別能被攔截的原因是,如果子類和父類在同一個包中是能覆蓋的。
在cglib代理情況下, execution(* *(..)) 可以攔截 public/protected/包級別方法(即這些方法都是能代理的)。
如果想要實現攔截private方法的 可以使用 原生 AspectJ 編譯期/運行期織入。