Spring AOP注解失效的坑及JDK動態代理


@Transactional @Async等注解不起作用

之前很多人在使用Spring中的@Transactional, @Async等注解時,都多少碰到過注解不起作用的情況。

為什么會出現這些情況呢?因為這些注解的功能實際上都是Spring AOP實現的,而其實現原理是通過代理實現的。

JDK動態代理

以一個簡單的例子理解一下JDK動態代理的基本原理:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

//目標類接口

public interface JDKProxyTestService {

  void run();

}

 

//目標類

public class JDKProxyTestServiceImpl implements JDKProxyTestService {

  public void run(){

    System.out.println("do something...");

  }

}

 

//代理類

public class TestJDKProxy implements InvocationHandler {

 

  private Object targetObject; //代理目標對象

 

  //構造代理對象

  public Object newProxy(Object targetObject) {

    this.targetObject = targetObject;

    return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),

        targetObject.getClass().getInterfaces(), this);

  }

 

  //利用反射,在原邏輯上進行邏輯增強

  public Object invoke(Object proxy, Method method, Object[] args)

      throws Throwable {

    //模擬事務開始

    assumeBeginTransaction();

    //原執行邏輯

    Object ret = method.invoke(targetObject, args);

    //模擬事務提交

    assumeCommitTransaction();

    return ret;

  }

 

  private void assumeBeginTransaction() {

    System.out.println("模擬事務開始...");

  }

 

  private void assumeCommitTransaction() {

    System.out.println("模擬事務提交...");

  }

}

 

//測試

public class Test {

  public static void main(String[] args) {

    TestJDKProxy jdkProxy = new TestJDKProxy();

    JDKProxyTestService proxy = (JDKProxyTestService) jdkProxy.newProxy(new JDKProxyTestServiceImpl());

    proxy.run();

  }

}

上面的例子應該能夠清楚的解釋JDK動態代理的原理了。它利用反射機制,生成了一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。我們通過代理類對象調用方法時,實際上會先調用其invoke方法,里面再調用原方法。這樣我們可以在原方法邏輯的前后統一添加處理邏輯。

Spring還有一種動態代理方式是CGLIB動態代理。它是把代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。雖然處理方式不一樣,但是代理的思想都是一致的。

如果被代理的目標對象實現了接口,那么Spring會默認使用JDK動態代理。所有該目標類型實現的接口都將被代理。若該目標對象沒有實現任何接口,則創建一個CGLIB代理。

Spring AOP注解失效及解決

基於以上對於動態代理原理的分析,我們來看以下兩個常見的問題:

同一個類中,方法A調用方法B(方法B上加有注解),注解無效

針對所有的Spring AOP注解,Spring在掃描bean的時候如果發現有此類注解,那么會動態構造一個代理對象。

如果你想要通過類X的對象直接調用其中帶注解的A方法,此注解是有效的。因為此時,Spring會判斷你將要調用的方法上存在AOP注解,那么會使用類X的代理對象調用A方法。

但是假設類X中的A方法會調用帶注解的B方法,而你依然想要通過類X對象調用A方法,那么B方法上的注解是無效的。因為此時Spring判斷你調用的A並無注解,所以使用的還是原對象而非代理對象。接下來A再調用B時,在原對象內B方法的注解當然無效了。

解決方法:

最簡單的方式當然是可以讓方法A和B沒有依賴,能夠直接通過類X的對象調用B方法。

但是很多時候可能我們的邏輯拆成這樣寫並不好,那么就還有一種方法:想辦法手動拿到代理對象。

AopContext類有一個currentProxy()方法,能夠直接拿到當前類的代理對象。那么以上的例子,就可以這樣解決:

?

1

2

3

4

5

// 在A方法內部調用B方法

// 1.直接調用B,注解失效。

B()

// 2.拿到代理類對象,再調用B。

((X)AopContext.currentProxy()).B()

AOP注解方法里使用@Autowired對象為null

在之前的使用中,出現過在加上注解的方法中,使用其他注入的對象時,發現對象並沒有被注入進來,為null。

最終發現,導致這種情況的原因是因為方法為private。因為Spring不管使用的是JDK動態代理還是CGLIB動態代理,一個是針對實現接口的類,一個是通過子類實現。無論是接口還是父類,顯然都不能出現private方法,否則子類或實現類都不能覆蓋到。

如果方法為private,那么在代理過程中,根本找不到這個方法,引起代理對象創建出現問題,也導致了有的對象沒有注入進去。

所以如果方法需要使用AOP注解,請把它設置為非private方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM