[Spring Boot實戰系列]
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()
函式中,我們呼叫myservice
的sayHello
函式,並向前臺傳送字串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形式。在這裡就不贅述,個人還是比較喜歡這種在通知上宣告函式路徑的方式,有興趣的同學可以在網上調研學習一下。另外所有程式碼基本都在文章裡,就不在上傳完整專案了。