Contents

Spring Boot AOP 小記

之前我同事推坑 Spring Boot 框架給我,跟我說 Sping 重要觀念有兩個,一個是 注入 另一個是 AOP 。目前注入常常使用,但是不常使用 AOP,最近用點時間整理一下。

觀念

推薦

AspectJ报错:error at ::0 can’t find referenced pointcut XXX - 楼兰胡杨 - 博客园

菜鳥工程師 肉豬: Spring AOP get method advice annotation and parameters

4.3.3 处理通知中的参数 - Spring 实战(第四版)

AOP錯誤: error at ::0 can’t find referenced pointcut XXX

 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.example.demo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Component
@Aspect
public class TestAspect {
    ....

    @Around("com.example.demo.TestClass.callMethod()")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }
}

可參考:AspectJ报错:error at ::0 can’t find referenced pointcut XXX - 楼兰胡杨 - 博客园
https://i.imgur.com/axe7Is2.png

不過我參照上面方法都失敗,所以我後來嘗試加上execution,順利解決問題。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    @Around("execution(* com.example.demo.TestClass.callMethod())")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }

名詞小記

https://i.imgur.com/yAKJwxs.png
框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习) - 小不点啊 - 博客园

解說名詞:

其實當初看敘述有看沒有懂,對照敘述我的理解先記錄下來,之後回頭複習希望能馬上進入狀況。

簡單我的觀點來看,Spring @Transactional都會用代理模式,這邊AOP其實也類似做一樣動作,底層(應該)用做代理模式去做,達成這個效果。

  • JoinPoint: 中文叫連接點,多個執行點可執行自訂要跑的程式(Advice)。
  • PointCut: 中文叫斷點,其中一個被選定的JoinPoint。
  • Advice: 就是你當PointCut 觸發要執行的程式。

程式配置可以參考:
https://i.imgur.com/X5sC4oK.jpg

記錄重點

pom.xml

不知道為什麼 Spring Initializr 找不到這個,要自己加到 pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

程式

src/main/java/com/example/demo/DemoApplication.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
		var testClass = ctx.getBean(TestClass.class);
		testClass.callMethod();
	}

}

src/main/java/com/example/demo/TestClass.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TestClass {

    @Autowired
    TestClass _testClass;

    public void callMethod() {
        System.out.println("Hello World!!!!你好世界");
        _testClass.callMethod2();
    }


    public void callMethod2() {
        System.out.println(" callMethod2");
    }

}

src/main/java/com/example/demo/TestAspect.java

 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
@Component
@Aspect
public class TestAspect {
    @Before("execution(* callMethod())")
    public void before(){
        System.out.println("我在你前面做");
    }

    @After("execution(* callMethod())")
    public void after(){
        System.out.println("我在你後面做");
    }

    @Around("execution(* com.example.demo.TestClass.callMethod())")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }
}

https://i.imgur.com/D5diOgP.png

其他重點

https://i.imgur.com/RsDFE3p.png

https://i.imgur.com/5r14L8C.png

參考:Spring AOP中JoinPoint的用法 - 简书

Object[] getArgs(); 獲取傳入目標方法的引數物件
Object getTarget(); 獲取被代理的物件
Object getThis(); 獲取代理物件

https://i.imgur.com/FAFbPAK.png
參考:Spring AOP概念Aspect、Advice、JoinPoint、JoinCut與Execution_Anur的博客-CSDN博客_advice aspect

https://i.imgur.com/767mPE7.png
參考:4.3.3 处理通知中的参数 - Spring 实战(第四版)

感想

這邊源碼我有丟到 Github: malagege/spring-boot-aop-example: 範例測試

我以為很難,其實寫出範例又好像很簡單。後來想到滿多機制能用這個寫,像重試API測試,我們也可以透過AOP方式,後來我很好奇有沒有別人現成寫好的工具,發現spring-retry可以做到這些事情。

文章整理:

彩蛋

依賴注入循環問題。

1
spring.main.allow-circular-references=true

.Net Core 另外 Retry 方案