摘要:自定義注解不生效原因解析及解決方法背景項目中,自己基于實現了一套緩存注解。但是最近出現一種情況緩存竟然沒有生效,大量請求被擊穿到層,導致壓力過大。至此,問題得到解決。
自定義注解不生效原因解析及解決方法 背景:
項目中,自己基于spring AOP實現了一套java緩存注解。但是最近出現一種情況:緩存竟然沒有生效,大量請求被擊穿到db層,導致db壓力過大。現在我們看一下具體代碼情形(代碼為偽代碼,只是為了說明一下具體情況)。
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注解并沒有生效。
分析:這是為什么呢?別急,我們帶著這個問題去看了一下注解的實現類。(這里就不貼緩存注解的實現代碼了)我的自定義注解是直接extends AbstractBeanFactoryPointcutAdvisor類然后實現其中的getPointcut() 和 getAdvice() 實現的。(其實這里可以直接使用aop環繞通知的,原理都差不多,我是為了熟悉源碼才這樣寫的)。
接下來,我們繼續往下分析,我們都知道基于spring aop實現的注解,在spring 中,如果有aop實現,那么容器注入的是該類的代理類,這里的代理類是aop 動態代理生成的代理類。Spring aop 的動態代理有兩種:一種是jdk的動態代理,一種是基于CGLIB的。這兩個的區別我就不多說了,如果你的業務類是基于接口實現的,則使用jdk動態代理,否則使用CGLIB動態代理。 我這里使用的是接口實現,所以我們就順著思路去看一下jdk動態代理的具體實現。
上邊的業務代碼類我已經貼出。而需要生成代理對象(proxy),分成兩步:
生成代理對象需要建立代理對象(proxy)和真實對象(AImpl)的代理關系
實現代理方法
在JDK動態代理中需要實現接口:java.lang.reflect.InvocationHandler.
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的調用走代理類就可以了。(就是這么簡單)
AProxy類我們是可以在spring容器中得到的。下面是修改后的解決方案:
method1(..) { ... ... // 如果希望調用的內部方法也被攔截,那么必須用過上下文獲取代理對象執行調用,而不能直接內部調用,否則無法攔截 if(null != AopContext.currentProxy()){ AopContext.currentProxy().method2(); }else{ method2(); } }
這里的AopContext.currentProxy() 拿到的實際就是代理對象了,這樣通過代理對象去調用method2肯定就沒有問題了。
還有一種解決方法就是不使用 動態代理織入,使用aspectJ織入,aspectJ直接在源類上進行字節碼的插入,而不是以代理的方式進行。
這里可以參考一下
AspectJ 編譯時織入(Compile Time Weaving, CTW)
因為這樣改動比較大,所以目前我還是采用第一種方案解決問題了。至此,問題得到解決。
總結:結合Spring aop動態代理的實現原理,提供兩種動態代理:JDK代理和CGLIB代理
JDK代理只能對實現了接口的類生成代理,而不能針對類;
CGLIB是針對類實現代理的,主要對指定的類生成一個子類,并覆蓋其中的方法,
因為是繼承,所以不能使用final來修飾類或方法。所以該類或方法最好不要聲明成final
更加詳細的解釋可以參考這篇博文 哪些方法不能實施Spring AOP事務
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69019.html
摘要:由于的限制,無法替換被代理類已經被載入的字節碼,只能生成并載入一個新的子類作為代理類,被代理類的字節碼依然存在于中。區別于前兩者,是一種靜態代理的實現,即在編譯時或者載入類時直接修改被代理類文件的字節碼,而非運行時實時生成代理。 現象描述 上周同事發現其基于mySql實現的分布式鎖的線上代碼存在問題,代碼簡化如下: @Controller class XService { @A...
摘要:和上標注的約束都會被執行注意如果子類覆蓋了父類的方法,那么子類和父類的約束都會被校驗。 每篇一句 沒有任何技術方案會是一種銀彈,任何東西都是有利弊的 相關閱讀 【小家Java】深入了解數據校驗:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【小家Spring】Spring方法級別數據校...
摘要:畢竟永遠相信本文能給你帶來意想不到的收獲使用示例關于數據校驗這一塊在中的使用案例,我相信但凡有點經驗的程序員應該沒有不會使用的,并且還不乏熟練的選手。 每篇一句 NBA里有兩大笑話:一是科比沒天賦,二是詹姆斯沒技術 相關閱讀 【小家Java】深入了解數據校驗:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validati...
摘要:介紹這里有官方提供的演示項目和介紹本筆記也是通過官方提供的演示項目來進行講解我們可以看到官方的項目中有三個模塊和其中是演示如何使用自動配置是自動配置時的一些邏輯處理比較簡單其中只有一些項目的依賴比如我們使用的 介紹 這里有官方提供的 演示項目 和 介紹. 本筆記也是通過官方提供的演示項目來進行講解. 我們可以看到官方的項目中有三個模塊, hornetq-sample-app horne...
摘要:這里有一個參數,主要是用來指定該配置項在配置文件中的前綴。創建一個配置類,里面沒有顯式聲明任何的,然后將剛才創建的導入。創建實現類,返回的全類名。創建實現類,實現方法直接手動注冊一個名叫的到容器中。前言 小伙伴們是否想起曾經被 SSM 整合支配的恐懼?相信很多小伙伴都是有過這樣的經歷的,一大堆配置問題,各種排除掃描,導入一個新的依賴又得添加新的配置。自從有了 SpringBoot 之后,咋...
閱讀 1184·2021-10-11 10:59
閱讀 1966·2021-09-29 09:44
閱讀 857·2021-09-01 10:32
閱讀 1431·2019-08-30 14:21
閱讀 1875·2019-08-29 15:39
閱讀 2982·2019-08-29 13:45
閱讀 3539·2019-08-29 13:27
閱讀 2012·2019-08-29 12:27