關(guān)于AOP的原理網(wǎng)上有很多教程,此處不再贅述,只是通過具體的案例來記錄如何使用。
一、入門案例
首先從start.spring.io上下載一個Spring Boot工程,只需要引入web依賴即可。然后我們創(chuàng)建一個Controller:
@RestController
public class HelloController {
/*@AspectAction(name = "loglog")*/
@GetMapping("/getHello")
public String getHello(){
System.out.println("執(zhí)行g(shù)etHello!");
return "hello";
}
}
然后創(chuàng)建一個切面:
@Aspect
@Component
public class LogAspect {
/**
* 前置通知
* 在方法執(zhí)行之前執(zhí)行
*/
@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void before() {
System.out.println("before");
}
}
這個切面中最重要的就是@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))"),它說明了這是一個前置通知,并且切入點為HelloController類中的getHello方法。
到這里一個最簡單的AOP例子就能運行了,啟動程序后,訪問入口,打印信息如下:
before
執(zhí)行g(shù)etHello!
二、通知類型
總共支持五種通知類型:
@Aspect
@Component
public class LogAspect {
/**
* 前置通知
* 在方法執(zhí)行之前執(zhí)行
*/
@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void before() {
System.out.println("before");
}
/**
* 后置通知
* 在方法執(zhí)行之后
*/
@After("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void after() {
System.out.println("after");
}
/**
* 返回通知
* 在方法執(zhí)行之后,返回之前執(zhí)行
*/
@AfterReturning("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void afterReturing(){
System.out.println("afterReturning");
}
/**
* 異常通知
* 在方法拋出異常之后執(zhí)行
*/
@AfterThrowing("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void afterThrowing(){
System.out.println("afterThrowing");
}
}
執(zhí)行結(jié)果可以表現(xiàn)各個通知的執(zhí)行順序:
before
執(zhí)行g(shù)etHello!
afterReturning
after
還有一個環(huán)繞通知,可以代替其它四個通知,環(huán)繞通知可以實現(xiàn)其它四個通知。
/**
* 環(huán)繞通知
* 能夠代替其它四種通知使用
*/
@Around("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("前置通知!");
try {
// 執(zhí)行目標(biāo)方法中的邏輯
joinPoint.proceed();
System.out.println("返回通知!");
} catch (Throwable throwable) {
System.out.println("異常通知!");
} finally {
System.out.println("后置通知!");
}
}
假設(shè)同時設(shè)置了四種通知,還有環(huán)繞通知,那么執(zhí)行的順序是怎樣的呢?
前置通知!
before
執(zhí)行g(shù)etHello!
afterReturning
after
返回通知!
后置通知!
需要注意的是,環(huán)繞通知中的joinPoint.proceed();如果不寫的話,就會跳過切入點目標(biāo)方法的邏輯執(zhí)行,直接執(zhí)行環(huán)繞通知后續(xù)的通知邏輯:
前置通知!
返回通知!
后置通知!
三、切點表達(dá)式
在如上的案例中,我們發(fā)現(xiàn)如果定義了很多通知,在定義切入點表達(dá)式時寫了很多一樣的東西:
"execution(* com.example.aopdemo.controller.HelloController.getHello(..))"
其實可以在切面類中先定義一個切點表達(dá)式,然后其它通知定義的時候引用這個切點表達(dá)式即可:
@Aspect
@Component
public class LogAspect {
/**
* 聲明切點表達(dá)式
*/
@Pointcut("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void pointCut(){}
/**
* 環(huán)繞通知
* 能夠代替其它四種通知使用
*/
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("前置通知!");
try {
//joinPoint.proceed();
System.out.println("返回通知!");
} catch (Throwable throwable) {
System.out.println("異常通知!");
} finally {
System.out.println("后置通知!");
}
}
}
四、切點指示器
以上面的切點表達(dá)式為例:
execution表示匹配執(zhí)行目標(biāo)方法,還有其它類型的指示器,但用的不多;*表示不關(guān)心方法的返回值;..表示不關(guān)心方法的參數(shù);-
如果需要多個匹配條件可以使用
&& || !來表示且、或、非的關(guān)系;@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")其中within是和execution平行的指示器,表示匹配的切入點類型;bean是實例匹配器,只有遇到girl這個實例的時候才會執(zhí)行AOP;
五、使自定義注解
有的時候我們希望在聲明自定義注解的方法上執(zhí)行AOP的邏輯,而不是如上指定具體的位置。
首先我們需要先定義自己的注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AspectAction {
String name() default "default";
}
然后,在切面中定義切入點為標(biāo)注這個注解的地方。
@Aspect
@Component
public class LogAspect {
@Pointcut("@annotation(com.example.aopdemo.annotation.AspectAction)")
public void pointCut(){}
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("前置通知!");
try {
joinPoint.proceed();
System.out.println("返回通知!");
} catch (Throwable throwable) {
System.out.println("異常通知!");
} finally {
System.out.println("后置通知!");
}
}
}
最后,只要在想使用AOP的方法之上加上自定義注解即可:
@AspectAction(name = "loglog")
@GetMapping("/getHello")
public String getHello(){
System.out.println("執(zhí)行g(shù)etHello!");
return "hello";
}
我們在環(huán)繞通知中,可以通過ProceedingJoinPoint來獲取如下各種信息,ProceedingJoinPoint繼承自JoinPoint,所以在其它類型的通知中也可以獲得如下內(nèi)容:
- 方法簽名;
- 方法入?yún)ⅲ?/li>
- 等等,參考源代碼即可;