分析java 中aspectj切面執(zhí)行兩次的原因
背景
轉眼之間,發(fā)現(xiàn)博客已經(jīng)將近半年沒更新了,甚是慚愧。話不多說,正如標題所言,最近在使用aspectj的時候,發(fā)現(xiàn)攔截器(aop切面)執(zhí)行了兩次了。我們知道,aspectj是aop的一種解決方案,本質上是通過代理類在目標方法執(zhí)行通知(advice),然后由代理類再去調用目標方法。所以,從這點講,攔截器應該只會執(zhí)行一次。但是在測試的時候發(fā)現(xiàn)攔截器執(zhí)行了兩次。
問題重現(xiàn)
既然問題已經(jīng)明了,那么可以通過代碼簡單重現(xiàn)這個問題,從而更深層次分析到底是什么原因導致的。
定義一個注解:
1
2
3
4
5
6
7
8
9
|
package com.rhwayfun.aspect; import java.lang.annotation.*; @target ({elementtype.method}) @retention (retentionpolicy. class ) @documented public @interface statsservice { } |
為該注解定義切面:
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
|
package com.rhwayfun.aspect; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.around; import org.aspectj.lang.annotation.aspect; import org.slf4j.logger; import org.slf4j.loggerfactory; @aspect public class statsserviceinterceptor { private static logger log = loggerfactory.getlogger(statsserviceinterceptor. class ); @around ( "@annotation(statsservice)" ) public object invoke(proceedingjoinpoint pjp) { try { log.info( "before invoke target." ); return pjp.proceed(); } catch (throwable e) { log.error( "invoke occurs error:" , e); return null ; } finally { log.info( "after invoke target." ); } } } |
方法測試:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package com.rhwayfun; import com.rhwayfun.aspect.statsservice; import org.slf4j.logger; import org.slf4j.loggerfactory; import java.time.localdatetime; public class aspecttest { private static logger log = loggerfactory.getlogger(aspecttest. class ); public static void main(string[] args) { aspecttest.print(); } @statsservice public static void print(){ log.info( "now: {}" , localdatetime.now()); } } |
輸出結果:
debug分析
由于是靜態(tài)織入,所以可以通過反編譯工具查看編譯后的文件,如下:
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
|
public class aspecttest { private static logger log; private static final /* synthetic */ joinpoint$staticpart ajc$tjp_0; private static final /* synthetic */ joinpoint$staticpart ajc$tjp_1; public static void main(final string[] args) { statsserviceinterceptor.aspectof().invoke(((aroundclosure)new aspecttest$ajcclosure1(new object[] { factory.makejp(aspecttest.ajc$tjp_0, (object)null, (object)null) })).linkclosureandjoinpoint(0)); } @statsservice public static void print() { statsserviceinterceptor.aspectof().invoke(((aroundclosure)new aspecttest$ajcclosure3(new object[] { factory.makejp(aspecttest.ajc$tjp_1, (object)null, (object)null) })).linkclosureandjoinpoint(65536)); } static { ajc$preclinit(); aspecttest.log = loggerfactory.getlogger((class)aspecttest.class); } private static /* synthetic */ void ajc$preclinit() { final factory factory = new factory( "aspecttest.java" , ( class )aspecttest. class ); ajc$tjp_0 = factory.makesjp( "method-call" , (signature)factory.makemethodsig( "9" , "print" , "com.rhwayfun.aspecttest" , "" , "" , "" , "void" ), 17 ); ajc$tjp_1 = factory.makesjp( "method-execution" , (signature)factory.makemethodsig( "9" , "print" , "com.rhwayfun.aspecttest" , "" , "" , "" , "void" ), 22 ); } } |
請注意兩個連接點:ajc$tjp_0和ajc$tjp_1,這兩個連接點是產(chǎn)生兩次調用的關鍵,問題注解明明是加上print()方法上的,為什么main()方法也被注入了通知呢?正因為main()方法也織入了通知,所以就形成了a call b, b call print()的調用鏈,有兩次method-call,一次method-execution,method-execution才是我們的目標方法print(),所以我們才看到了兩次輸出。
1
|
method-call和method-execution都是連接點proceedingjoinpoint的kind屬性 |
其實,這屬于ajc編譯器的一個bug,詳見ajc-bug
所以,到這一步,問題就很清晰了,因為ajc編輯器的bug,導致了在main方法中也織入了通知,所以在執(zhí)行的時候,輸出了兩次日志。
解決方法
方案一
因為兩次調用的kind屬性不一樣,所以可以通過kind屬性來判斷時候調用切面。這樣顯得不優(yōu)雅,而且如果切面有更多的邏輯的話,需要加各種if-else的判斷,所以不推薦。
方法二
更優(yōu)雅的方案是修改@around("@annotation(statsservice)")的邏輯,改為@around("execution(* *(..)) && @annotation(statsservice)")。
重新運行上面的測試類,結果如下:
如有疑問請留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
原文鏈接:http://blog.csdn.net/u011116672/article/details/63685340