Spring AOP 實現函數執行時間打印

AOP 概念

AOP:Aspect Orient Programming
中文翻譯:面向切面編程
作爲面向對象編程的一種補充,採用非侵入式技術手段處理系統中分佈於各個模塊的橫切關注點,比如事務管理,安全審計,日誌、緩存等等。

AOP 優缺點

優點:原有代碼無需更改,不影響原有業務邏輯;
缺點:通過代理方式,會有一定的性能損失;

AOP 代理

AOP 的底層實現是通過代理和反射技術,攔截正常的方法調用,在正常方法調用完成前和完成後插入切面相關的代碼。具體實現有如下兩種:

  • 靜態代理:
    代表:AspectJ,直接通過修改字節碼實現,編譯期完成,但是需要額外的編譯器支持;
  • 動態代理:
    代表:Spring AOP,運行時動態創建代理類;
    Spring AOP 代理又分爲兩種,JDK Proxy 和 CGLIB,分別針對接口代理和類代理。

AOP 關鍵點:

  • Aspect:切面
    對應一個類,用於實現橫跨多個企業應用類的業務處理,比如事務,日誌等。
    通俗的話來說,其他應用類實現業務的主要邏輯(縱向),切面關注次要方面(橫向插入日誌,事務處理,安全審計等動作)。
  • Join Point:連接點
    方法執行,異常處理,改變對象的值等特定情況;
  • Advice:動作
    與連接點和切點相關的一系列動作
    對應一個方法
  • Pointcut:切點
    與連接點相關的表達式,匹配則執行相關動作(Advice);
  • Target Object:目標對象
    加入相關的動作處理(Advice)的目標對象;
  • AOP Proxy:代理
    Spring AOP 通過 JDK Proxy 或者 CGLIB 創建代理;
  • Weaving:織入
    關聯切面和目標對象的過程。可以在編譯期,加載期或運行期完成;
    Spring AOP 在運行期完成這個過程,AspectJ在編譯期完成這個過程;

AOP 實現舉例:(函數執行時間打印)

基於常用的 Spring AOP 爲例,基於註解實現打印函數執行時間的切面功能。

增加依賴包

創建 Spring 項目,在 pom.xml 加入 Spring AOP 相關的類。

#pom.xml

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${spring.version}</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.8.14</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.14</version>
</dependency>
開啓切面能力

proxyTargetClass = true,強制使用 CGLIB 代理,如果不設置針對於接口方法織入,默認使用 JDK Proxy 代理。

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
 public class AspectJConfiguration {
 }
定義註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CallTime {

}
創建切面
@Aspect
@Component
public class CallTimeAspect {
 
}
增加切點
@Aspect
@Component
public class CallTimeAspect {
  @Pointcut("@annotation(CallTime) && execution(public org.test.service.*(..))") 
  public void pointCutMethod();
}

"@annotation(CallTime) && execution(public org.test.service.*(..))"
這個表達式表示只攔截加有CallTime註解的,並且是public方法,並且是 org.test.service這個包下面的類的方法,只有匹配這個表達式的方法纔會被攔截,當然這個表達式可以更復雜,比如針對參數做匹配等等。

增加連接點和動作

Spring 只支持方法連接點,主要有3種方式對方法進行增強:
@Before 方法執行前;
@After 方法執行後;
@Around 方法執行過程中;

@Aspect
@Component
public class CallTimeAspect {
  @Pointcut("@annotation(CallTime) && execution(public org.test.service.*(..))") 
  public void pointCutMethod();

  @Around("pointCutMethod()")
  public Object callTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object proceed = joinPoint.proceed();
    long end = System.currentTimeMillis();
    String className = proceed.getSignature().getDeclaringType().getSimpleName();
    String functionName = proceed.getSignature().getName();
    System.out.println(String.format("%s.%s takes %dms", className, functionName, end - start));
    return proceed;
  }
}
增加攔截代碼
# org.test.service

public class BusinessService {

   @CallTime
   void doBusiness() {
      ...
   }
}

當doBusiness在外部被調用時,就會打印執行時間了:

BusinessService.doBusiness takes XXXms
潛在的問題
  • 被final修飾的類無法被改寫,因此也就沒法使用 AOP 進行攔截;
  • 在外部調用被攔截的方法中再調用該類其他的方法,無法使用 AOP 進行攔截;
class Test {
  @CallTime
  public void funcA() {
     this.funcB();
  }
  
  @CallTime
  public void funcB() {
  }

在外部調用:

new Test().funcA()

只有 funcA 會被攔截,funcB 並不會。

  • 私有方法不能被外部調用,因此也就沒法使用 AOP 進行攔截;

Spring AOP 參考

https://www.journaldev.com/2583/spring-aop-example-tutorial-aspect-advice-pointcut-joinpoint-annotations

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章