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形式。在這裏就不贅述,個人還是比較喜歡這種在通知上聲明函數路徑的方式,有興趣的同學可以在網上調研學習一下。另外所有代碼基本都在文章裏,就不在上傳完整項目了。