6. Spring - AOP - 基本使用

系列篇幅

前言

本文來整理一下AOP相關知識,這裏不直接介紹理論了,我們看下日常工作中如何使用

Filter、Interceptor、ControllerAdvice、AOP的關係

在這裏插入圖片描述

流程說明

  1. 加入依賴 spring-aspects
  2. 將業務邏輯組件和切面類都加入到容器中,並將切面類加入註解 @Aspect
  3. 在切面類上的每一個通知方法上標註通知註解,告訴Spring何時何地運行 切入點表達式
  4. 開啓基於註解的aop模式 @EnableAspectJAutoProxy
通知方法 說明
前置通知(@Before) 在目標方法運行之前運行
後置通知(@After) 在目標方法正常結束/異常結束之後運行
返回通知(@AfterReturning) 在目標方法正常返回之後運行
異常通知(@AfterThrowing) 在目標方法出現異常之後運行
環繞通知(@Around) 動態代理,手動推進目標方法運行 joinPoint.procced()

引入依賴

lombok 推薦: Lombok 的使用

<!-- lombok 個人習慣非必須 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>
<!-- aop切面必須導入 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>

常用方法說明

JoinPoint參數必須放在第一位

在某個類方法執行前調用

@After("execution(public int com.xm.demo.MathCalculator.*(..))")
public void logEnd(JoinPoint joinPoint){
  String methodName = joinPoint.getSignature().getName();
  System.out.printf("方法:%s 結束,切面類:logEnd 方法", methodName);
}

抽離切面公共表達式,方便閱讀與封裝

@Pointcut("execution(public int com.xm.demo.MathCalculator.*(..))")
public void pointCut() {
}

/**
 * 本類的公共切入表達式
 */
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
  Object[] args = joinPoint.getArgs();
  String methodName = joinPoint.getSignature().getName();
  System.out.printf("方法:%s 即將運行,參數列表:%s,切面類:logStart 方法", 
                    methodName, Arrays.asList(args));
}

/**
 * 其他類的公共切入表達式
 */
@After("com.xm.demo.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint) {
  String methodName = joinPoint.getSignature().getName();
  System.out.printf("方法:%s 結束,切面類:logEnd 方法", methodName);
}

完整例子

定義功能類,等待切面日誌

public class MathCalculator {
    public int div(int i, int j) {
        System.out.printf("方法:MathCalculator.div(%d,%d)\n", i, j);
        return i / j;
    }
}

定義切面類@Aspect 一定要加,否則無效

@Aspect
public class LogAspects {

  /**
   * 抽取公共的切入點表達式
   */
  @Pointcut("execution(public int com.xm.demo.MathCalculator.*(..))")
  public void pointCut() {
  }


  @Before("pointCut()")
  public void logStart(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 即將運行,參數列表:%s,切面類:logStart 方法\n",
                      methodName, Arrays.asList(args));
  }

  @After("com.xm.demo.LogAspects.pointCut()")
  public void logEnd(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 結束,切面類:logEnd 方法\n", methodName);
  }

  @AfterReturning(value = "pointCut()", returning = "result")
  public void logReturn(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 正常返回,返回結果:%s,切面類:logReturn方法\n",
                      methodName, result);
  }

  @AfterThrowing(value = "pointCut()", throwing = "exception")
  public void logException(JoinPoint joinPoint, Exception exception) {
    String methodName = joinPoint.getSignature().getName();
    System.out.printf("方法:%s 異常,exception:%s,切面類:logException方法\n",
                      methodName, exception);
  }

}

配置類 @EnableAspectJAutoProxy 一定要加,否則無效

@EnableAspectJAutoProxy
@Configuration
public class MainConfig {

    /**
     * 業務邏輯類加入容器中
     */
    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }

    /**
     * 切面類加入到容器中
     */
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }

}

使用

public class MainTest {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
      new AnnotationConfigApplicationContext(MainConfig.class);
    // 不實用ioc容器的話,切面是無效的
    MathCalculator calculator = context.getBean(MathCalculator.class);
    int div = calculator.div(4, 2);
    System.out.printf("result:%d", div);
  }
}

正常結果

方法:div 即將運行,參數列表:[4, 2],切面類:logStart 方法
方法:MathCalculator.div(4,2)
方法:div 結束,切面類:logEnd 方法
方法:div 正常返回,返回結果:2,切面類:logReturn方法
result:2

失敗結果

方法:div 即將運行,參數列表:[1, 0],切面類:logStart 方法
方法:MathCalculator.div(1,0)
方法:div 結束,切面類:logEnd 方法
方法:div 異常,exception:java.lang.ArithmeticException: / by zero,切面類:logException方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章