一. Spring中的代理
JDK動態代理:
其代理對象必須是某個接口的實現,它是通過運行期間創建一個接口的實現類來完成對目標對象的代理.
CGLIB代理:
實現原理類似於JDK的動態代理,只是它在運行期間生成目標類擴展的子類對象.(也就是通過繼承的方式創建的代理類).CGLIB是高效的代碼生成包,底層是依靠ASM(開源的Java字節碼編輯類庫)操字節碼實現的,性能比JDK強.
- 基於註解使用@AspectJ風格的切面申明
@Configuration
@ComponentScan(basePackageClasses = com.yveshe.usage1.Performance.class)
@EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = false)
public class ApplicationContextConf {
}
- 基於XML使用@AspectJ風格的切面聲明
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="false" />
- 基於XMl使用schema風格的切面申明
<aop:config proxy-target-class="true" expose-proxy="false" >
...
</aop:config>
proxy-target-class 不顯示配置,默認爲false,代表使用JDK動態代理,除非目標類無實現接口,則轉爲CGLIB代理
expose-proxy 不顯示配置,默認爲false
二. 屬性說明
-
proxy-target-class:
Spring AOP 部分使用JDK動態代理或者CGLIB來爲目標對象創建代理.(建議儘量使用JDK的動態代理),如果被代理的目標對象實現了至少一個接口,則會使用JDK動態代理,所以該目標類型實現的接口都將被代理.
也就是說當目標對象實現了接口,則默認使用JDK的動態代理,若該目標對象沒有實現任何接口,則創建一個CGLIB代理,如果你希望強制使用CGLIB代理,則需要將該參數顯示的設置成true.
注意:
1.使用JDK動態代理,只能代理該目標對象實現接口的方法,並不能代理實現類中自己定義的方法.
2.如果你希望代理目標對象的所有方法,而不只是實現自接口的方式,可以使用CGLIB代理,本質是擴展目標對象類,但是需要考慮一下兩個問題:
1)無法通知(advise)Final方法,因爲他們不能被覆蓋寫
2)你需要將CGLIB的jar包放在classpath下面
3.當需要使用CGLIB代理和@AspectJ自動代理支持,使用該代碼設置<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="false" />
-
expose-proxy :
expose-proxy。爲是否暴露當前代理對象爲ThreadLocal模式。
有時候目標對象內部的自我調用將無法實施切面中的增強
我們先來看一個問題: @Transacitonal註解的方法被另外一個方法調用的時候,事務是不生效的。具體不生效代碼如下public interface UserService{ public void a(); public void b(); } public class UserServiceImpl implements UserService{ @Transactional(propagation = Propagation.REQUIRED) public void a(){ this.b(); } @Transactional(propagation = Propagation.REQUIRED_NEW) public void b(){ System.out.println("b has been called"); } }
分析:
SpringAOP對於最外層的函數只攔截public方法,不攔截protected和private方法,另外不會對最外層的public方法內部調用的其他方法也進行攔截,即只停留於代理對象所調用的方法。所以我們會發現如果調用a方法,在b方法中出現異常時,事務不生效.
解決辦法:
答案就是在<aop:aspectj-autoproxy />
中設置expose-proxy屬性爲true暴露代理.<aop:aspectj-autoproxy expose-proxy=“true”>
,然後使用AopContext.currentProxy()獲取當前代理,將this.b()改爲((UserService)AopContext.currentProxy()).b(),這樣就生效了。
分析參考: https://www.cnblogs.com/chihirotan/p/7356683.html
三. 總結
-
關於JDK和CGLIB方式的總結:
如果目標對象實現了接口,默認情況會採用JDK自動代理實現AOP
如果目標對象實現了接口,可以強制使用CGLIB實現AOP
如果目標對象沒有實現接口,必須採用CGLIB庫,Spring會在JDK動態代理和CGLIB之間轉換. -
如何強制使用CGLIB實現AOP?
1.添加CGLIB依賴庫
2.在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true" />
-
JDK動態代理和CGLIB字節碼生成的區別?
JDK動態代理只能對實現了接口的類生成代理,而不能針對類.
CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因爲是繼承,所以該類或方法最好不要聲明成final.