項目中,本身基於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
在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事務