sping AOP切面註解使用中的坑–同一個類中嵌套方法註解不生效
在開發過程中,監控方法運行時間,然後獲取程序運行的瓶頸,是一個常見的優化步驟。寫一個spring切面實現的註解來實現函數運行時間的監控看上去是一個比較直接的解決方案。
使用spring AOP實現方法運行時間的日誌打印,其實比較簡單:
首先定義一個註解:
//com.TimeLog
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeLog {
}
然後實現該註解的切面:
//com.TimeLogAspect
@Aspect
@Component
public class TimeLogAspect {
@Pointcut("@annotation(com.TimeLog)")
public void handlingTimePointcut() {}
//定義一個環繞切面
@Around("handlingTimePointcut()")
public Object handlingTimeAround(ProceedingJoinPoint joinPoint){
try {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
System.out.println(proceed);
//打印方法執行時間
System.out.println("方法執行時間:" + (System.currentTimeMillis() - start));
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
在確保這個切面的bean被注入之後,可以使用@TimeLog註解在函數上,如:
class Test{
@TimeLog
void test(){
for(int i=0; i < 20000; ++i)
Symtem.out.println(i);
}
}
//輸出結果:方法執行時間:121
看似問題已經完美解決,但是其實由於spring AOP的內部實現原因,會存在一個bug。
如:
class Test{
@TimeLog
void test(){
for(int i=0; i < 20000; ++i)
Symtem.out.println(i);
test2();
}
@TimeLog
void test2(){
}
}
在test中調用test2並不會輸出test2的運行時間,即使test2也被@TimeLog修飾。爲什麼會這樣呢?簡單解釋是這樣的:
spring AOP的內部實現是對Test類進行了代理,如:
clase TestProxy {
private Test test;
...
public test(){
doBefore();
test.test(); // 1
doAfter();
}
public test2() {
doBefore();
test.test2();
doAfter();
}
public doBefore() {
//切面邏輯。代理會在被註解的方法調用開始前調用這個方法,
//執行切面邏輯
}
public doAfter() {
//切面邏輯。代理會在被註解的方法調用結束後調用這個方法,
//執行切面邏輯
}
}
主要的問題出在標註1處,在執行test.test()時,內部直接調用test.test2(),而不是重新生成新代理執行代理的test2()。
由於直接調用this.test2(),而不是代理的test2方法,所以doBefore()和doAfter()都不能不執行,註解的切面就不會生效。
那麼,如何才能寫一個打印合理的方法運行時間的註解呢?可以參考bytebuddy解決spring AOP切面同一個類嵌套方法不生效問題