1. 程式人生 > >Spring AOP 切點(pointcut)表示式

Spring AOP 切點(pointcut)表示式

概括

這遍文章將介紹Spring AOP切點表示式(下稱表示式)語言,首先介紹兩個面向切面程式設計中使用到的術語。

  • 連線點(Joint Point):廣義上來講,方法、異常處理塊、欄位這些程式呼叫過程中可以抽像成一個執行步驟(或者說執行點)的單元。從Spring AOP來講,就是指java的方法和異常處理程式碼塊。

  • 切點(Pointcut):是連線點的描述定義,Spring AOP通過切點來定位到哪些連線點。切點表示式語言就是切點用來定義連線點的語法。

用例

表示式會出現在以下幾種場景

  1. 作為@Pointcut的引數,用以定義連線點
    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositoryClassMethods() {}

    在上面的程式碼片段中的註解@Pointcut的引數"within(@org.springframework.stereotype.Reposity *)"就是使用的切點表示式。而上程式碼中的repositoryClassMethods()方法被AOP AspectJ定義為切點簽名方法,作用是使得通知的註解可以通過這個切點簽名方法連線到切點,通過解釋切點表示式找到需要被切入的連線點。最終的目的都是為了找到需要被切入的連線點。像下面這段程式碼片段

@Around("repositoryClassMethods()")
public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
    ...
}

如果你的專案是基於xml配置的,可以在<aop:pointcut>標籤裡配置表示式來定位連線點,參考以下程式碼片段:

<aop:config>
    <aop:pointcut id="anyDaoMethod"
      expression="@target(org.springframework.stereotype.Repository)"/>
</aop:config>

切點指示符

切點指示符是切點定義的關鍵字,切點表示式以切點指示符開始。開發人員使切點指示符來告訴切點將要匹配什麼,有以下9種切點指示符:execution、within、this、target、args、@target、@args、@within、@annotation,下面一一介結這9種切點指示符。

execution

execution是一種使用頻率比較高比較主要的一種切點指示符,用來匹配方法簽名,方法簽名使用全限定名,包括訪問修飾符(public/private/protected)、返回型別,包名、類名、方法名、引數,其中返回型別,包名,類名,方法,引數是必須的,如下面程式碼片段所示:

@Pointcut("execution(public String org.baeldung.dao.FooDao.findById(Long))")

上面的程式碼片段裡的表示式精確地匹配到FooDao類裡的findById(Long)方法,但是這看起來不是很靈活。假設我們要匹配FooDao類的所有方法,這些方法可能會有不同的方法名,不同的返回值,不同的引數列表,為了達到這種效果,我們可以使用萬用字元。如下程式碼片段所示:

@Pointcut("execution(* org.baeldung.dao.FooDao.*(..))")

第一個萬用字元匹配所有返回值型別,第二個匹配這個類裡的所有方法,()括號表示引數列表,括號裡的用兩個點號表示匹配任意個引數,包括0個

within

使用within切點批示符可以達到上面例子一樣的效果,within用來限定連線點屬於某個確定型別的類。如下面程式碼的效果與上面的例子是一樣的:

@Pointcut("within(org.baeldung.dao.FooDao)")

我們也可以使用within指示符來匹配某個包下面所有類的方法(包括子包下面的所有類方法),如下程式碼所示:

@Pointcut("within(org.baeldung..*)")

this 和 target

this用來匹配的連線點所屬的物件引用是某個特定型別的例項,target用來匹配的連線點所屬目標物件必須是指定型別的例項;那麼這兩個有什麼區別呢?原來AspectJ在實現代理時有兩種方式:
1、如果當前物件引用的型別沒有實現自介面時,spring aop使用生成一個基於CGLIB的代理類實現切面程式設計
2、如果當前物件引用實現了某個介面時,Spring aop使用JDK的動態代理機制來實現切面程式設計
this指示符就是用來匹配基於CGLIB的代理類,通俗的來講就是,如果當前要代理的類物件沒有實現某個介面的話,則使用this;target指示符用於基於JDK動態代理的代理類,通俗的來講就是如果當前要代理的目標物件有實現了某個介面的話,則使用target.:

public class FooDao implements BarDao {
    ...
}

比如在上面這段程式碼示例中,spring aop將使用jdk的動態代理來實現切面程式設計,在編寫匹配這型別的目標物件的連線點表示式時要使用target指示符, 如下所示:

@Pointcut("target(org.baeldung.dao.BarDao)")

如果FooDao類沒有實現任何介面,或者在spring aop配置屬性:proxyTargetClass設為true時,Spring Aop會使用基於CGLIB的動態位元組碼技為目標物件生成一個子類將為代理類,這時應該使用this指示器:

@Pointcut("this(org.baeldung.dao.FooDao)")

引數

引數指示符是一對括號所括的內容,用來匹配指定方法引數:


@Pointcut("execution(* *..find*(Long))")

這個切點匹配所有以find開頭的方法,並且只一個Long類的引數。如果我們想要匹配一個有任意個引數,但是第一個引數必須是Long類的,我們這可使用下面這個切點表示式:

@Pointcut("execution(* *..find*(Long,..))")

@Target

這個指示器匹配指定連線點,這個連線點所屬的目標物件的類有一個指定的註解:

@Pointcut("@target(org.springframework.stereotype.Repository)")

@args

這個指示符是用來匹配連線點的引數的,@args指出連線點在執行時傳過來的引數的類必須要有指定的註解,假設我們希望切入所有在執行時接受實@Entity註解的bean物件的方法:

@Pointcut("@args(org.baeldung.aop.annotations.Entity)")
public void methodsAcceptingEntities() {}

為了在切面裡接收並使用這個被@Entity的物件,我們需要提供一個引數給切面通知:JointPoint:

@Before("methodsAcceptingEntities()")
public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) {
    logger.info("Accepting beans with @Entity annotation: " + jp.getArgs()[0]);
}

@within

這個指示器,指定匹配必須包括某個註解的的類裡的所有連線點:

@Pointcut("@within(org.springframework.stereotype.Repository)")

上面的切點跟以下這個切點是等效的:

@Pointcut("within(@org.springframework.stereotype.Repository *)")

@annotation

這個指示器匹配那些有指定註解的連線點,比如,我們可以新建一個這樣的註解@Loggable:

@Pointcut("@annotation(org.baeldung.aop.annotations.Loggable)")
public void loggableMethods() {}

我們可以使用@Loggable註解標記哪些方法執行需要輸出日誌:

@Before("loggableMethods()")
public void logMethod(JoinPoint jp) {
    String methodName = jp.getSignature().getName();
    logger.info("Executing method: " + methodName);
}

切點表示式組合

可以使用&&、||、!、三種運算子來組合切點表示式,表示與或非的關係。

@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}

@Pointcut("execution(* *..create*(Long,..))")
public void firstLongParamMethods() {}

@Pointcut("repositoryMethods() && firstLongParamMethods()")
public void entityCreationMethods() {}

作者的github例子:https://github.com/eugenp/tutorials/tree/master/spring-mvc-java

from:http://blog.51cto.com/5914679/2092253