Spring自定義註解不生效緣由解析及解決方法

自定義註解不生效緣由解析及解決方法

背景:

項目中,本身基於spring AOP實現了一套java緩存註解。可是最近出現一種狀況:緩存居然沒有生效,大量請求被擊穿到db層,致使db壓力過大。如今咱們看一下具體代碼情形(代碼爲僞代碼,只是爲了說明一下具體狀況)。java

interface A {
    int method1(..);
    int method2(..);
    ... ...
}

class AImpl implements A {
    @Override
    @CacheMM(second=600)      //這裏的@CacheMM就是我實現的自定義緩存註解
    public int method1(..) {
        ... ...
        method2(..);
        ... ...
    }
    
    @Override
    @CacheMM(second=600)
    public int method2(..) {
        ... ...
    }
}

如上代碼,當調用method1時,發現method2註解並無生效。spring

分析:

這是爲何呢?別急,咱們帶着這個問題去看了一下註解的實現類。(這裏就不貼緩存註解的實現代碼了)個人自定義註解是直接extends AbstractBeanFactoryPointcutAdvisor類而後實現其中的getPointcut() 和 getAdvice() 實現的。(其實這裏能夠直接使用aop環繞通知的,原理都差很少,我是爲了熟悉源碼才這樣寫的)。 緩存

接下來,咱們繼續往下分析,咱們都知道基於spring aop實現的註解,在spring 中,若是有aop實現,那麼容器注入的是該類的代理類,這裏的代理類是aop 動態代理生成的代理類。Spring aop 的動態代理有兩種:一種是jdk的動態代理,一種是基於CGLIB的。這兩個的區別我就很少說了,若是你的業務類是基於接口實現的,則使用jdk動態代理,不然使用CGLIB動態代理。 我這裏使用的是接口實現,因此咱們就順着思路去看一下jdk動態代理的具體實現。 ide

上邊的業務代碼類我已經貼出。而須要生成代理對象(proxy),分紅兩步:this

  1. 生成代理對象須要創建代理對象(proxy)和真實對象(AImpl)的代理關係
  2. 實現代理方法

在JDK動態代理中須要實現接口:java.lang.reflect.InvocationHandler..net

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;   
import java.lang.reflect.Proxy;   
  
public class AProxy implements InvocationHandler   
{   
    private Object target;   
      
    /**  
     * 生成代理對象,並和真實服務對象綁定.  
     * @param target 真實服務對象  
     * @return 代理對象 */   
    public Object bind(Object target)   
    {   
        this.target = target;   
          
        //生成代理對象,並綁定.   
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), //類的加載器   
                              target.getClass().getInterfaces(), //對象的接口,明確代理對象掛在哪些接口下   
                              this);//指明代理類,this表明用當前類對象,那麼就要求其實現InvocationHandler接口的invoke方法   
          
        return proxy;   
    }   
          
    /**  
     * 當生成代理對象時,第三個指定使用AProxy進行代理時,代理對象調用的方法就會進入這個方法。  
     * @param proxy 代理對象  
     * @param method  被調用的方法  
     * @param args 方法參數  
     * @return 代理方法返回。  
     * @throws Throwable  異常處理 */   
     @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable   
     {   
        System.err.println("反射真實對象方法前");   
        Object obj = method.invoke(target, args);//至關於AImpl類中對應方法調用.   
        System.err.println("反射真實對象方法後");   
          
        return obj;   
    }  
}

代碼中,Object obj = method.invoke(target,args) 經過反射調度真實對象的方法,這個很重要。咱們知道其實雖然aop是經過代理對象去實現一些附加的操做的,可是真正的類方法調用仍是經過反射調用真實對象的。這個時候,咱們回頭看一下問題,咱們AImpl中有兩個方法,其中method2是在method1內部調用的。當調用method1時,spring內部其實調用的是代理類AProxy類的invoke,這個時候在執行真實對象方法錢去執行method1中的一些附加操做。而後,在經過反射進入對應AImpl類中調用method1方法。注意,這個時候,已經不在代理對象中操做了,因爲method2的調用是在method1內部調用的,因此在這裏實際調用method2的是真實對象,並非代理對象。 因此,就致使method2上的緩存註解沒有生效。代理

解決:

好了,如今知道問題的緣由後(動態代理的坑啊,內部調用不走代理類,因此實現的附加操做確定不會執行了),咱們來針對性的解決。咱們如今知道這個實際上是由於實際執行的不是代理類而致使的,那咱們解決的思路就想辦法讓method2的調用走代理類就能夠了。(就是這麼簡單) code

AProxy類咱們是能夠在spring容器中獲得的。下面是修改後的解決方案:對象

method1(..) {
    ... ...
     // 若是但願調用的內部方法也被攔截,那麼必須用過上下文獲取代理對象執行調用,而不能直接內部調用,不然沒法攔截  
        if(null != AopContext.currentProxy()){  
            AopContext.currentProxy().method2();  
        }else{  
            method2();  
        }      

}

這裏的AopContext.currentProxy() 拿到的實際就是代理對象了,這樣經過代理對象去調用method2確定就沒有問題了。 blog

還有一種解決方法就是不使用 動態代理織入,使用aspectJ織入,aspectJ直接在源類上進行字節碼的插入,而不是以代理的方式進行。

這裏能夠參考一下
AspectJ 編譯時織入(Compile Time Weaving, CTW)

由於這樣改動比較大,因此目前我仍是採用第一種方案解決問題了。至此,問題獲得解決。

總結:

結合Spring aop動態代理的實現原理,提供兩種動態代理:JDK代理和CGLIB代理

JDK代理只能對實現了接口的類生成代理,而不能針對類;
CGLIB是針對類實現代理的,主要對指定的類生成一個子類,並覆蓋其中的方法,
由於是繼承,因此不能使用final來修飾類或方法。因此該類或方法最好不要聲明成final

更加詳細的解釋能夠參考這篇博文 哪些方法不能實施Spring AOP事務

相關文章
相關標籤/搜索