spring AOP注解失效原因分析及解決過(guò)程
@Transactional @Async等注解不起作用
之前很多人在使用Spring中的@Transactional, @Async等注解時(shí),都多少碰到過(guò)注解不起作用的情況。
為什么會(huì)出現(xiàn)這些情況呢?因?yàn)檫@些注解的功能實(shí)際上都是Spring AOP實(shí)現(xiàn)的,而其實(shí)現(xiàn)原理是通過(guò)代理實(shí)現(xiàn)的。
JDK動(dòng)態(tài)代理
以一個(gè)簡(jiǎn)單的例子理解一下JDK動(dòng)態(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)對(duì)象
//構(gòu)造代理對(duì)象
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ù)開(kāi)始
assumeBeginTransaction();
//原執(zhí)行邏輯
Object ret = method.invoke(targetObject, args);
//模擬事務(wù)提交
assumeCommitTransaction();
return ret;
}
private void assumeBeginTransaction() {
System.out.println("模擬事務(wù)開(kāi)始...");
}
private void assumeCommitTransaction() {
System.out.println("模擬事務(wù)提交...");
}
}
//測(cè)試
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動(dòng)態(tài)代理的原理了。它利用反射機(jī)制,生成了一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來(lái)處理。我們通過(guò)代理類對(duì)象調(diào)用方法時(shí),實(shí)際上會(huì)先調(diào)用其invoke方法,里面再調(diào)用原方法。這樣我們可以在原方法邏輯的前后統(tǒng)一添加處理邏輯。
Spring還有一種動(dòng)態(tài)代理方式是CGLIB動(dòng)態(tài)代理。它是把代理對(duì)象類的class文件加載進(jìn)來(lái),通過(guò)修改其字節(jié)碼生成子類來(lái)處理。雖然處理方式不一樣,但是代理的思想都是一致的。
如果被代理的目標(biāo)對(duì)象實(shí)現(xiàn)了接口,那么Spring會(huì)默認(rèn)使用JDK動(dòng)態(tài)代理。所有該目標(biāo)類型實(shí)現(xiàn)的接口都將被代理。若該目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)任何接口,則創(chuàng)建一個(gè)CGLIB代理。
Spring AOP注解失效原因及解決
基于以上對(duì)于動(dòng)態(tài)代理原理的分析,我們來(lái)看以下兩個(gè)常見(jiàn)的問(wèn)題:
原因1:同一個(gè)類中,方法A調(diào)用方法B(方法B上加有注解),注解無(wú)效
針對(duì)所有的Spring AOP注解,Spring在掃描bean的時(shí)候如果發(fā)現(xiàn)有此類注解,那么會(huì)動(dòng)態(tài)構(gòu)造一個(gè)代理對(duì)象。
如果你想要通過(guò)類X的對(duì)象直接調(diào)用其中帶注解的A方法,此注解是有效的。因?yàn)榇藭r(shí),Spring會(huì)判斷你將要調(diào)用的方法上存在AOP注解,那么會(huì)使用類X的代理對(duì)象調(diào)用A方法。
但是假設(shè)類X中的A方法會(huì)調(diào)用帶注解的B方法,而你依然想要通過(guò)類X對(duì)象調(diào)用A方法,那么B方法上的注解是無(wú)效的。因?yàn)?strong>此時(shí)Spring判斷你調(diào)用的A并無(wú)注解,所以使用的還是原對(duì)象而非代理對(duì)象。接下來(lái)A再調(diào)用B時(shí),在原對(duì)象內(nèi)B方法的注解當(dāng)然無(wú)效了。
解決方法:
最簡(jiǎn)單的方式當(dāng)然是可以讓方法A和B沒(méi)有依賴,能夠直接通過(guò)類X的對(duì)象調(diào)用B方法。
但是很多時(shí)候可能我們的邏輯拆成這樣寫(xiě)并不好,那么就還有一種方法:想辦法手動(dòng)拿到代理對(duì)象。
AopContext類有一個(gè)currentProxy()方法,能夠直接拿到當(dāng)前類的代理對(duì)象。那么以上的例子,就可以這樣解決:
// 在A方法內(nèi)部調(diào)用B方法 // 1.直接調(diào)用B,注解失效。 B() // 2.拿到代理類對(duì)象,再調(diào)用B。 ((X)AopContext.currentProxy()).B()
原因2:AOP注解方法里使用@Autowired對(duì)象為null
在之前的使用中,出現(xiàn)過(guò)在加上注解的方法中,使用其他注入的對(duì)象時(shí),發(fā)現(xiàn)對(duì)象并沒(méi)有被注入進(jìn)來(lái),為null。
最終發(fā)現(xiàn),導(dǎo)致這種情況的原因是因?yàn)榉椒閜rivate。因?yàn)镾pring不管使用的是JDK動(dòng)態(tài)代理還是CGLIB動(dòng)態(tài)代理,一個(gè)是針對(duì)實(shí)現(xiàn)接口的類,一個(gè)是通過(guò)子類實(shí)現(xiàn)。無(wú)論是接口還是父類,顯然都不能出現(xiàn)private方法,否則子類或?qū)崿F(xiàn)類都不能覆蓋到。
如果方法為private,那么在代理過(guò)程中,根本找不到這個(gè)方法,引起代理對(duì)象創(chuàng)建出現(xiàn)問(wèn)題,也導(dǎo)致了有的對(duì)象沒(méi)有注入進(jìn)去。
所以如果方法需要使用AOP注解,請(qǐng)把它設(shè)置為非private方法。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Spring AOP底層源碼深度解析之從動(dòng)態(tài)代理到注解實(shí)現(xiàn)的完整架構(gòu)
- 自定義注解SpringBoot防重復(fù)提交AOP方法詳解
- Spring?AOP注解配置與XML配置雙實(shí)戰(zhàn)教程
- SpringBoot整合Jasypt使用自定義注解+AOP實(shí)現(xiàn)敏感字段加解密
- 使用SpringAOP自定義權(quán)限控制注解的實(shí)現(xiàn)示例
- Springboot項(xiàng)目使用AOP與自定義注解記錄請(qǐng)求日志方式
- Spring?AOP的注解實(shí)現(xiàn)方式實(shí)例詳解
相關(guān)文章
Spring實(shí)戰(zhàn)之使用@Resource配置依賴操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之使用@Resource配置依賴操作,結(jié)合實(shí)例形式分析了Spring使用@Resource配置依賴具體步驟、實(shí)現(xiàn)及測(cè)試案例,需要的朋友可以參考下2019-12-12
Java 字符串操作之contains 和 substring 方法最佳實(shí)踐與常
本文給大家詳細(xì)介紹Java 字符串操作之contains 和 substring 方法最佳實(shí)踐與常見(jiàn)問(wèn)題,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2025-09-09
springboot?vue測(cè)試前端項(xiàng)目管理列表分頁(yè)功能實(shí)現(xiàn)
這篇文章主要為大家介紹了springboot?vue測(cè)試前端項(xiàng)目列表分頁(yè)功能實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
mybatis中一對(duì)一、一對(duì)多的<association> 配置使用
<association>?是 MyBatis 中處理一對(duì)一或一對(duì)多關(guān)系的映射元素,用于處理一對(duì)一和一對(duì)多關(guān)系的映射,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-05-05
Spring?WebFlux?與?WebClient?使用指南及最佳實(shí)踐
WebClient是Spring WebFlux模塊提供的非阻塞、響應(yīng)式HTTP客戶端,基于Project Reactor實(shí)現(xiàn),適用于高并發(fā)場(chǎng)景spring?webclient,本文給大家介紹Spring?WebFlux與WebClient使用指南及最佳實(shí)踐,感興趣的朋友一起看看吧2025-07-07
IntelliJ?IDEA?代碼運(yùn)行時(shí)中文出現(xiàn)亂碼問(wèn)題及解決方法
在我們剛接觸到IDEA時(shí),想美滋滋的敲一個(gè)“hello?world”來(lái)問(wèn)候這個(gè)世界,但難免會(huì)遇到這種問(wèn)題亂碼,這篇文章主要介紹了解決IntelliJ?IDEA?代碼運(yùn)行時(shí)中文出現(xiàn)亂碼問(wèn)題,需要的朋友可以參考下2023-09-09

