Spring AOP注解失效的坑及JDK動態(tài)代理
@Transactional @Async等注解不起作用
之前很多人在使用Spring中的@Transactional, @Async等注解時(shí),都多少碰到過注解不起作用的情況。
為什么會出現(xiàn)這些情況呢?因?yàn)檫@些注解的功能實(shí)際上都是Spring AOP實(shí)現(xiàn)的,而其實(shí)現(xiàn)原理是通過代理實(shí)現(xiàn)的。
JDK動態(tài)代理
以一個(gè)簡單的例子理解一下JDK動態(tài)代理的基本原理:
//目標(biāo)類接口 public interface JDKProxyTestService { void run(); } //目標(biāo)類 public class JDKProxyTestServiceImpl implements JDKProxyTestService { public void run(){ System.out.println("do something..."); } } //代理類 public class TestJDKProxy implements InvocationHandler { private Object targetObject; //代理目標(biāo)對象 //構(gòu)造代理對象 public Object newProxy(Object targetObject) { this.targetObject = targetObject; return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); } //利用反射,在原邏輯上進(jìn)行邏輯增強(qiáng) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //模擬事務(wù)開始 assumeBeginTransaction(); //原執(zhí)行邏輯 Object ret = method.invoke(targetObject, args); //模擬事務(wù)提交 assumeCommitTransaction(); return ret; } private void assumeBeginTransaction() { System.out.println("模擬事務(wù)開始..."); } private void assumeCommitTransaction() { System.out.println("模擬事務(wù)提交..."); } } //測試 public class Test { public static void main(String[] args) { TestJDKProxy jdkProxy = new TestJDKProxy(); JDKProxyTestService proxy = (JDKProxyTestService) jdkProxy.newProxy(new JDKProxyTestServiceImpl()); proxy.run(); } }
上面的例子應(yīng)該能夠清楚的解釋JDK動態(tài)代理的原理了。它利用反射機(jī)制,生成了一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理。我們通過代理類對象調(diào)用方法時(shí),實(shí)際上會先調(diào)用其invoke方法,里面再調(diào)用原方法。這樣我們可以在原方法邏輯的前后統(tǒng)一添加處理邏輯。
Spring還有一種動態(tài)代理方式是CGLIB動態(tài)代理。它是把代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。雖然處理方式不一樣,但是代理的思想都是一致的。
如果被代理的目標(biāo)對象實(shí)現(xiàn)了接口,那么Spring會默認(rèn)使用JDK動態(tài)代理。所有該目標(biāo)類型實(shí)現(xiàn)的接口都將被代理。若該目標(biāo)對象沒有實(shí)現(xiàn)任何接口,則創(chuàng)建一個(gè)CGLIB代理。
Spring AOP注解失效及解決
基于以上對于動態(tài)代理原理的分析,我們來看以下兩個(gè)常見的問題:
同一個(gè)類中,方法A調(diào)用方法B(方法B上加有注解),注解無效
針對所有的Spring AOP注解,Spring在掃描bean的時(shí)候如果發(fā)現(xiàn)有此類注解,那么會動態(tài)構(gòu)造一個(gè)代理對象。
如果你想要通過類X的對象直接調(diào)用其中帶注解的A方法,此注解是有效的。因?yàn)榇藭r(shí),Spring會判斷你將要調(diào)用的方法上存在AOP注解,那么會使用類X的代理對象調(diào)用A方法。
但是假設(shè)類X中的A方法會調(diào)用帶注解的B方法,而你依然想要通過類X對象調(diào)用A方法,那么B方法上的注解是無效的。因?yàn)榇藭r(shí)Spring判斷你調(diào)用的A并無注解,所以使用的還是原對象而非代理對象。接下來A再調(diào)用B時(shí),在原對象內(nèi)B方法的注解當(dāng)然無效了。
解決方法:
最簡單的方式當(dāng)然是可以讓方法A和B沒有依賴,能夠直接通過類X的對象調(diào)用B方法。
但是很多時(shí)候可能我們的邏輯拆成這樣寫并不好,那么就還有一種方法:想辦法手動拿到代理對象。
AopContext類有一個(gè)currentProxy()方法,能夠直接拿到當(dāng)前類的代理對象。那么以上的例子,就可以這樣解決:
// 在A方法內(nèi)部調(diào)用B方法 // 1.直接調(diào)用B,注解失效。 B() // 2.拿到代理類對象,再調(diào)用B。 ((X)AopContext.currentProxy()).B()
AOP注解方法里使用@Autowired對象為null
在之前的使用中,出現(xiàn)過在加上注解的方法中,使用其他注入的對象時(shí),發(fā)現(xiàn)對象并沒有被注入進(jìn)來,為null。
最終發(fā)現(xiàn),導(dǎo)致這種情況的原因是因?yàn)榉椒閜rivate。因?yàn)镾pring不管使用的是JDK動態(tài)代理還是CGLIB動態(tài)代理,一個(gè)是針對實(shí)現(xiàn)接口的類,一個(gè)是通過子類實(shí)現(xiàn)。無論是接口還是父類,顯然都不能出現(xiàn)private方法,否則子類或?qū)崿F(xiàn)類都不能覆蓋到。
如果方法為private,那么在代理過程中,根本找不到這個(gè)方法,引起代理對象創(chuàng)建出現(xiàn)問題,也導(dǎo)致了有的對象沒有注入進(jìn)去。
所以如果方法需要使用AOP注解,請把它設(shè)置為非private方法。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java使用CompletableFuture分批處理任務(wù)實(shí)現(xiàn)
本文主要介紹了java使用CompletableFuture分批處理任務(wù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07Flutter ListView 上拉加載更多下拉刷新功能實(shí)現(xiàn)方法
這篇文章主要介紹了Flutter ListView 上拉加載更多下拉刷新功能實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07Java中的ArrayList.trimToSize()方法詳解
這篇文章主要介紹了Java中的ArrayList.trimToSize()方法詳解,前幾天看了Java?ArrayList,沒有明白trimToSize()這個(gè)方法是什么意思,所以看了一下源碼并且debug一下自己的一個(gè)例子,明白了其中的含義,需要的朋友可以參考下2023-11-11Java的Struts2框架配合Ext JS處理JSON數(shù)據(jù)的使用示例
這篇文章主要介紹了Java的Struts2框架配合Ext JS處理JSON數(shù)據(jù)的使用示例,包括將Ext JS中的JSON數(shù)據(jù)解析為列表的方法,需要的朋友可以參考下2016-03-03idea中方法、注釋、導(dǎo)入類折疊或是展開的設(shè)置方法
這篇文章主要介紹了idea中方法、注釋、導(dǎo)入類折疊或是展開的設(shè)置,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04詳解MyBatis-Plus Wrapper條件構(gòu)造器查詢大全
這篇文章主要介紹了詳解MyBatis-Plus Wrapper條件構(gòu)造器查詢大全,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08