Spring AOP切點和通知
前言
Spring切面由切點和通知組成。Spring AOP是基於代理的,包括JDK動態代理或者CGLIB動態代理,只支援方法級別的連線點,因此Spring AspectJ風格切點表示式僅僅是AspectJ的子集。表示式之間可以用&&、||、!表示與或非、如果是在XML中配置,那麼需要用and、or、not代替&&、||、!。注意,切點的定義如果只到子類,那麼父類的方法無法被攔截。
通知
通知表示切面在何時完成什麼操作。Spring提供了5個註解來定義通知,註解的值可以是切點表示式,也可以是@Pointcut註解標註的方法,@Pointcut的值是切點表示式。@Before
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
@AfterReturning
通知方法會在目標方法返回後呼叫。如果想要將方法返回的引數傳遞給通知方法,可以這麼配置@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... }
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
returning="retVal"必須和通知方法doAccessCheck裡的引數名一樣,Object retVal表示可以接收任何型別的返回值。@AfterThrowing
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
throwing="ex"必須和通知方法doRecoveryActions裡的引數名一樣,DataAccessException ex表示可以接收DataAccessException型別的異常。@After
通知方法會在目標方法返回或丟擲異常後呼叫。@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
@Around
環繞通知,通知方法會將目標方法封裝起來,這樣就可以在目標方法前後做某些處理。這個註解標註的通知方法第一次引數必須是ProceedingJoinPoint型別的,ProceedingJoinPoint的proceed方法表示呼叫目標方法,此方法僅能呼叫一次。proceed還有個過載的方法,可以傳入一個Object陣列,如果呼叫ProceedingJoinPoint.getArgs()可以得到目標方法引數陣列,將這個陣列傳給proceed,就等於不傳引數的proceed。也可以自定義一個數組,傳給proceed,但是引數個數和型別需要和目標方法保持一致。@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
切點
切點表示切面在何處完成通知所定義的內容,Spring中切點只能是方法級別。execution
匹配方法,這是最為常用的表示式。格式:execution(訪問修飾符-可選 返回型別 方法所屬型別-可選 方法名(方法引數) 丟擲異常)
返回型別可以用*代表任意型別。
方法所屬型別是類的完全限定名,可以用*匹配任意字元,除了.,因此*無法匹配子包或者內部類,..表示一串以.開始和結束的字串,因此可以用來匹配子包和內部類。
方法名可以用*匹配任意字元。
方法引數可以用..表示任意數量任意型別的引數,*可以代表一個任意型別的引數,比如(*,String),表示第一個引數型別任意,第二個引數是String型別。
execution(public * *(..)):任意public方法
execution(* set*(..)):任意方法名以set開頭的方法
execution(* com.xyz.service.AccountService.*(..)):com.xyz.service.AccountService類中的任意方法
execution(* com.xyz.service.*.*(..)):com.xyz.service包中的任意型別的任意方法,不包括子包和內部類
execution(* com.xyz.service..*.*(..)):com.xyz.service包中的任意型別的任意方法,包括子包和內部類
within
匹配某些型別中的所有方法。
within(com.xyz.service.*):com.xyz.service包中的任意型別的所有方法,不包括子包和內部類
within(com.xyz.service..*)::com.xyz.service包中的任意型別的所有方法,不包括子包和內部類
@within
匹配某些型別中的所有方法,這些型別具有指定的註解。
@within(org.springframework.transaction.annotation.Transactional):具有@Transactional註解的類中的所有方法。
this
匹配代理類是某個型別的例項。
this(com.xyz.service.AccountService):代理類是com.xyz.service.AccountService的例項(繼承類或者實現介面)
target
匹配某個型別中的所有方法。
target(com.xyz.service.AccountService):com.xyz.service.AccountService中的所有方法。
@target
匹配某個型別中的某些方法,這些方法具有指定型別的註解。
@target(org.springframework.transaction.annotation.Transactional):目標類中具有@Transactional 註解的方法。
args
匹配執行時傳遞給方法的引數,引數具有指定的數量和型別。
args(java.io.Serializable):表示執行時傳遞給方法的只有一個引數,且引數是Serializable型別。和execution(* *(java.io.Serializable))不一樣,後者表示程式碼中宣告的方法引數型別是Serializable。
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
上面的例子是args另外一種用法,向通知方法傳參。args(account,..)表示至少有一個引數,且引數的型別是Account,引數值會被傳遞給通知方法中的account。
@args
匹配執行時傳遞給方法的引數,引數的型別要具有指定型別的註解。
@args(com.xyz.security.Classified):表示執行時傳遞給方法的只有一個引數,且引數具有@Classified註解。
@annotation
匹配方法,這個方法要具有指定型別的註解。
@annotation(org.springframework.transaction.annotation.Transactional):目標方法具有@Transactional 註解。