1. 程式人生 > >[Spring Boot實戰系列] - No.5 Spring boot AOP 示例

[Spring Boot實戰系列] - No.5 Spring boot AOP 示例

Spring boot AOP 示例

在之前的文章中,介紹過Spring 的AOP與AspectJ相關的內容。最近實驗室的一個專案又用到了springboot的AOP,在網上調研了一下發現了幾個配置極其簡單但功能很完善的示例,在這裡總結一下。AOP相關的原理及含義不再解釋,參考之前的文章。

1. 前期程式碼準備

建立一個Springboot專案,在專案中編寫一個IndexController,一個User實體類,以及一個service(為了簡單起見我直接編寫了Service的實現,而沒有按照介面-實現的方式)

IndexController

@RestController
public class IndexController { @Autowired MyService myService; @GetMapping(value = "hello") public String hello(){ myService.sayHello("Greet to everyone"); return "hhh"; } }

hello()函式中,我們呼叫myservicesayHello函式,並向前臺傳送字串hhh

User

public class User {
    private
int id; private String name; public User(){ } public User(int id,String name){ this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }

MyService

@Service
public class MyService
{ public void sayHello(String greet){ System.out.println("Hello, I'm fucntion sayHello of MyService"); } public void introduce(User user){ System.out.println("Hello My name is "+ user.getName()); } }
2. 定義切面

在專案中定義一個切面,如下所示:

RuleAspect

@Aspect
@Component
public class RuleAspect {

    @Pointcut("execution(* com.example.demo.service.MyService.sayHello(..))")
    public void pointCutName(){}

    @Before(value = "execution(* com.example.demo.service.MyService.sayHello(..))")
    public void beforeFunc(){
        System.out.println("Function before sayHello");
    }

    @After("pointCutName()")
    public void afterFunc(){
        System.out.println("Function after sayHello");
    }
    
    @Around("execution(* com.example.demo.controller.IndexController.hello(..))")
    public Object aroundHelloCtrl(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Before");
        Object res = proceedingJoinPoint.proceed();
        System.out.println("After");
        return res;
    }
}

要解釋的地方有以下幾點:

  • 定義切點有幾種方法,可以使用@Pointcut首先定義一個函式,然後在後面的註解中直接使用,例如程式碼中的@After標註的函式,也可像@Before的示例一樣,直接在註解後面寫明完整的函式路徑

  • @Before,@After,@Around分別對應在前置通知(在連線點執行之前)、後置通知,和環繞通知。三種通知的執行順序如下(不考慮@AfterReturning@AfterReturning)

在這裡插入圖片描述

  • 在環繞通知@Around中,我們看到函式簽名處有引數ProceedingJoinPoint,這個引數是獲取切入點函式的引數。我們可以看到,在示例函式中,我們首先列印Before,然後呼叫proceedingJoinPoint.proceed()來執行切入點函式,然後在函式結束後列印After

    同時,要注意的是,如果你的切入點函式有返回值,那麼@Around註解的通知函式一定也要有返回值,否則切入點函式不能正常返回結果

啟動程式,我們在postman中輸入http://localhost:8080/hello

控制檯結果如下:

Before
Function before sayHello
Hello, I'm fucntion sayHello of MyService
Function after sayHello
After

呼叫順序參見上面的順序圖

3. 獲取切入點函式中的引數

獲取切入點函式中的引數,在網上有很多采用反射的方法來獲取的。雖然也能很好的完成功能,但過程有點冗雜。還是推薦使用aspectj中基於註解傳遞引數的方法

我們向Controller中新增一個控制函式,專門用來展示連線點引數傳遞的效果。該函式從url中拿到引數作為函式形參

@GetMapping(value = "getArgs")
public String getArgs(@RequestParam("key")String key,@RequestParam("value") String value){
    return "hhh";
}

在切面中定義一個通知,獲取連線點中的引數值

@Around(value = "execution(* com.example.demo.controller.IndexController.getArgs(..)) && args(key,value)")
public Object aroundSayHello(ProceedingJoinPoint joinPoint,String key,String value) throws Throwable {
    System.out.printf("The args of this method is %s and %s \n",key,value);
    return joinPoint.proceed();
}

方法很簡單,在execution後邊新增 && args(key,value),並在函式的簽名處宣告對應的引數。要注意的是args()後面的引數,必須和切入點函式對應的簽名是一樣的,即形參的型別和個數、順序必須一樣,否則無法呼叫通知函式

postman中輸入以下地址:http://localhost:8080/getArgs?key=123&value=456

The args of this method is 123 and 456 

該方法不僅可以傳入基本型別,還可以傳入我們定義的實體,示例如下

Controller中新增控制函式:

@GetMapping(value = "intro")
public void intro(){
	myService.introduce(new User(123456,"ming"));
}

在切面中新新增一個環繞通知,用來測試接受切入點實體引數

@Before("execution(* com.example.demo.service.MyService.introduce(..)) && args(user)")
public void beforeIntro(User user){
    System.out.println(user.getName());
}

postman中輸入http://localhost:8080/intro

ming
Hello My name is ming
4. 使用註解宣告切入點

還有一種方法是自定義一個註解,然後在切入點函式上新增這個註解,即基於註解的AOP形式。在這裡就不贅述,個人還是比較喜歡這種在通知上宣告函式路徑的方式,有興趣的同學可以在網上調研學習一下。另外所有程式碼基本都在文章裡,就不在上傳完整專案了。