Spring @Aspect註解

使用場景

  • 常見用於記錄日誌, 異常集中處理, 權限驗證以及 Web參數有效驗證等等

列子1 (演示基本過程


@Aspect
@Component
public class TestAspect {
    /**
     * 聲明一個切入點, 命名 pointcut1
     * */
    @Pointcut("execution(public java.util.Map com.example.demo.controller.TestService.getMap(String, Integer))")
    private void pointcut1() {}

    /**
     * 鎖定的切點方法之前執行
     * JoinPoint 獲取連接點信息 
     *	-	Object[] getArgs() 			獲取連接點方法運行時的入參列表
     *	-	Signature getSignature() 	獲取連接點的方法簽名對象
     *	-	Object getTarget() 			獲取連接點所在的目標對象
     *	-	Object getThis() 			獲取代理對象
     * */
    @Before("pointcut1()")
    public void aspect1(JoinPoint joinPoint) {
        System.out.println("TestAspect -> @Before 方法名稱:" + joinPoint.getSignature().getName() + ", 參數:" + Arrays.asList(joinPoint.getArgs()));
    }

    /**
     * 鎖定的切點方法之後執行
     * */
    @After("pointcut1()")
    public void aspect2() {
        System.out.println("TestAspect -> @After");
    }

    /**
     * 鎖定的切點方法返回後執行
     * */
    @AfterReturning(pointcut = "pointcut1()", returning="result")
    public void aspect3(Object result){
        System.out.println("TestAspect -> @AfterReturning 返回值:" + result);
    }

}

@Service
public class TestService {
    public Map<String, Object> getMap(String name, Integer age) {
        final Map<String, Object> result = new HashMap<>(2);
        result.put("name", name);
        result.put("age", age);
        System.out.println("TestService -> getMap 函數體內輸出:" + result);
        return result;
    }

}

# 測試地址 http://127.0.0.1:8080/aspectTest
@RestController
public class AspectController {
    @Autowired
    private TestService testService;

    @GetMapping(value = "/aspectTest")
    public Map<String, Object> aspectTest(@RequestParam(value="name", required=false, defaultValue="大爺") String name,
                                          @RequestParam(value="age", required=false, defaultValue="35") Integer age) {
        System.out.println("AspectController -> aspectTest");
        return testService.getMap(name, age);
    }

}

# 輸出
AspectController -> aspectTest
TestAspect -> @Before 方法名稱:getMap, 參數:[大爺, 35]
TestService -> getMap 函數體內輸出:{name=大爺, age=35}
TestAspect -> @After
TestAspect -> @AfterReturning 返回值:{name=大爺, age=35}

切點表達式

  1. ..兩個點表明多個, *代表一個
  2. 其中權限修飾符是可選, 當不寫時不能用*代替, 因此第一個*代表返回類型不限
  3. 第二個*表示指定包下所有類
  4. 第三個*表示指定類下所有方法
  5. (..)兩個點表示指定方法的參數不限

@Pointcut(execution(* com..demo.controller.*.*(..)))

切點複合運算

  • 切點表達式可以加運算符 與&&, 或||, 非! 做複合運算

@Before(value="execution(* com.example.demo.controller.TestService.getMap(..)) && args(name, age, ..)")

切點匹配方法

  • execution: 用於匹配方法的執行
  • within: 用於匹配指定類內的方法的執行
  • args: 用於指定匹配方法的參數類型
  • target: 用於匹配容器內的類的對象執行方法, 不包括引入接口
  • this: 用於匹配容器內的類的對象執行方法, 包括引入接口

注: target和 this兩種方法表達式必須全限定名到類名, 不支持*通配符

通知註解

  • @Before: 前置通知, 在方法執行之前執行
  • @After: 後置通知, 在方法執行之後執行
  • @AfterRunning: 返回通知, 在方法返回結果之後執行

@AfterReturning(pointcut = "pointcut1()", returning="result")
public void aspect3(JoinPoint joinPoint, Object result){
	System.out.println("TestAspect1 -> @AfterReturning 返回值:" + result);
}

# 執行過程
Request -> @Before -> Method -> @After -> @AfterReturning	

  • @AfterThrowing: 異常通知, 在方法拋出異常之後執行, 意味着跳過返回通知

/**
* 指定方法內拋出異常時纔會被通知
* 屬性 throwing上設置異常類 Throwable將會捕獲任何錯誤和異常, 或按需設置指定異常類
*/
@AfterThrowing(pointcut = "pointcut1()", throwing="ex")
public void aspect4(JoinPoint joinPoint, TestException ex) {
	System.out.println("TestAspect1 -> @AfterThrowing 異常:" + ex.getMessage());
}

# 執行過程
Request -> @Before -> Method -> @After -> @AfterThrowing

  • @Around: 環繞通知, 圍繞着方法執行

/**
* ProceedingJoinPoint繼承於 JoinPoint接口, 並多了兩個方法
*	-	Object proceed() throws Throwable 		通過反射執行目標對象連接點處的方法
*	-	Object proceed(Object[] var1) throws Throwable 	通過入參替換原參, 通過反射執行目標對象連接點處的方法
*/
@Around("pointcut1()")
public Object aspect5(ProceedingJoinPoint pjp) throws Throwable {
	System.out.println("TestAspect1 -> @Around start");
	Object[] args = pjp.getArgs();
	for (Object arg : args) {
		System.out.println("TestAspect1 -> @Around 參數:" + arg); // 輸出指定方法參數
	}
	long startTime = System.currentTimeMillis();
	Object object = pjp.proceed(); // 調用 proceed後指定方法會被執行
	long endTime = System.currentTimeMillis();
	System.out.println("TestAspect1 -> @Around " + (endTime - startTime) + " ms");
	return object;
}

# 輸出
AspectController -> aspectTest
TestAspect1 -> @Around start
TestAspect1 -> @Around 參數:大爺
TestAspect1 -> @Around 參數:35
TestAspect1 -> @Before
TestService -> getMap 函數體內輸出:{name=大爺, age=35}
TestAspect1 -> @Around 0 ms
TestAspect1 -> @After
TestAspect1 -> @AfterReturning 返回值:{name=大爺, age=35}

# 正常執行過程
Request -> @Around -> @Before -> Method -> @Around -> @After -> @AfterReturning
# 異常執行過程
Request -> @Around -> @Before -> Method -> @Around -> @After -> @AfterThrowing

切面類執行順序

  • 通過 @Order註解設置, 數越小越靠前

@Aspect
@Component
@Order(3)
public class TestAspect1 {}

如果您覺得有幫助,歡迎點贊哦 ~ 謝謝!!

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