1. 程式人生 > >Spring AOP切點和通知

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 註解。