Spring AOP切點表達式函數

摘要: Spring中的AspectJ切點表達式函數 切點表達式函數就像我們的GPS導航軟件。通過切點表達式函數,再配合通配符和邏輯運算符的靈活運用,我們能很好定位到我們需要織入增強的連接點上。經過上面的鋪墊,下面來看看Springz中支持的切點表

Spring中的AspectJ切點表達式函數

切點表達式函數就像我們的GPS導航軟件。通過切點表達式函數,再配合通配符和邏輯運算符的靈活運用,我們能很好定位到我們需要織入增強的連接點上。經過上面的鋪墊,下面來看看Springz中支持的切點表達式函數。

1. 方法切點函數

函數 入參 說明 示例
execution() 方法匹配字符串 滿足某一匹配模式的的所有目標類方法連接點 execution(* com.yc.service.*.*(..))在配置service層的事務管理時常用,定位於任意返回類型(第一個”*”) 在com.yc.service包下的所有類(第二個”*”)下的所有方法(第三個”*”),且這個方法的入參爲任意類型、數量(體現在 “(..)“)
@annotation() 方法註解類名 標註了特定註解的目標方法連接點上 @anntation(com.yc.controller.needRecord),定位於controller層中任何添加@needRecord的方法,這可以方便地對控制層中某些方法被調用(如某人某時間登陸、進入後臺管理界面)添加日誌記錄。

1. execution詳解

execution的語法表達式如下:execution(<修飾符> <返回類型> <類路徑> <方法名>(<參數列表>) <異常模式> ) 
其中,修飾符和異常是可選的,如果不加類路徑,則默認對所有的類生效。它常用實例如下:

1. 通過方法簽名、返回值定義切點:

- `execution(public * *Service(..))`:定位於所有類下返回值任意、方法入參類型、數量任意,public類型的方法
- `execution(public String *Service(..))`:定位於所有類下返回值爲String、方法入參類型、數量任意,public類型的方法

2. 通過類包定義切點:

- `execution(* com.yc.controller.BaseController+.*(..))`:匹配任意返回類型,對應包下BaseController類及其子類等任意方法。
- `execution(* com.*.(..))`:匹配任意返回類型,com包下所有類的所有方法
- `execution(* com..*.(..))`:匹配任意返回類型,com包、子包下所有類的所有方法

注意.表示該包下所有類,..則涵括其子包。

3. 通過方法入參定義切點

- 這裏“\*”表示任意類型的一個參數,“..”表示任意類型任意數量的參數
- `execution(* speak(Integer,*))`:匹配任意返回類型,所有類中只有兩個入參,第一個入參爲Integer,第二個入參任意的方法
- `execution(* speak(..,Integer,..))`:匹配任意返回類型,所有類中至少有一個Integer入參,但位置任意的方法。

2. annotation詳解

此註解用於定位標註了某個註解的目標切點。下面我們來看一個模擬用戶登錄成功後日志記錄用戶名、時間和調用方法的示例,

1. 自定義註解

@Retention(RetentionPolicy.CLASS)//生命註釋保留時長,這裏無需反射使用,使用CLASS級別
@Target(ElementType.METHOD)//生命可以使用此註解的元素級別類型(如類、方法變量等)
public @interface NeedRecord {
}

關於自定義註解的更多屬性與說明,可查看我的另一篇文章http://blog.csdn.net/qwe6112071/article/details/50949663

2. 定義切面(配置增強和定位切點)

@Aspect//將當前類標註成一個切面。
public class Annotation_aspect {
    @AfterReturning("@annotation(test.aop2.NeedRecord)")//這裏指向註解類
    public void Record(JoinPoint joinPoint){//切點入參。
        System.out.println("日誌記錄:用戶" +joinPoint.getArgs()[0] + "在" + new SimpleDateFormat("yyyy-MM-dd hh:mm;ss").format(new Date()) + "調用了"+ joinPoint.getSignature()+"方法" );
    }
}

3. 定義目標對象

@NeedRecord
public void login(String name){
    System.out.println("I'm "+name+" ,I'm logining");
}

4. 配置IOC容器

<aop:aspectj-autoproxy />   <!-- 使@AspectJ註解生效 -->
<bean class="test.aop2.Annotation_aspect" /><!-- 註冊切面,使AOP自動識別並進行AOP方面的配置 -->
<bean id="userController" class="test.aop2.UserController" /><!-- 註冊目標對象 -->

這裏需注意: 
1. 和 標註在目標對象Annotation_aspect的註解@Aspect缺一不可,否則調用login方法時,Record方法不會被調用 
2. 必須在IOC容器中註冊切面和目標對象,以便在下面測試中通過

5. 測試

public static void main(String args[]){
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:test/aop2/aop.xml");
        UserController userController = (UserController) ac.getBean("userController");
        userController.login("zenghao");
    }

調用測試方法後,會打印信息: 
I’m zenghao ,I’m logining 
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean ‘test.aop2.Annotation_aspect#0’//log4j的日誌記錄打印 
日誌記錄:用戶zenghao在2016-03-21 08:27;48調用了void test.aop2.UserController.login(String)方法

2. 方法入參切點函數

函數 入參 說明 示例
args() 類名 定位於入參爲特定類型的的方法 如args(com.yc.model.User,com.yc.model.Article),我們要定位於所有以User,Article爲入參的方法,需要注意的是,類型的個數、順序必須都一一對應)
@args() 類型註解類名 定位於被特定註解的類作爲方法入參的連接點 @args(com.yc.annotation.MyAnnotation)。MyAnnotation爲自定義註解,標註在目標對象方法入參上,被標註的目標都會被匹配。,如方法public myMethod(@MyAnnotation String args);

1. args()詳解:

args函數接受一個類名或變量名(對應與目標對象方法的入參),並將該類名綁定到增強方法入參中。對上一個實例,我們將切面改造成:

@After("args(name)")
public void Record(JoinPoint joinPoint,String name) throws Throwable{
    System.out.println("日誌記錄:用戶" +name+ "在" + new SimpleDateFormat("yyyy-MM-dd hh:mm;ss").format(new Date()) + "調用了"+ joinPoint.getSignature().getDeclaringTypeName()+"方法" );
    /*控制檯打印
    I'm zenghao ,I'm logining
    日誌記錄:用戶zenghao在2016-03-21 09:32;31調用了test.aop2.UserController方法
    */
}

在這裏有幾點是值得注意的: 
1. 在本例中,我們不能使用args(String)來匹配,因爲我們在方法入參中加入了變量name,必須通過args()綁定連接點入參的機制:通過在方法聲明中定義入參String name,然後args會搜尋方法定義中命名參數來獲取對應的參數類型(這裏是String)。 
2. 如果我們使用args(name),但在方法定義體中沒聲明String name,則會報錯 java.lang.IllegalArgumentException: warning no match for this type name: name [Xlint:invalidAbsoluteTypeName] 
3. 在這裏如果要使用args(String)匹配我們的UserController中的方法,則必須去掉方法定義中的String name參數,或將註解改成@After(value = "args(name)",argNames = "name")方可。否則會拋出異常java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 
4. 如果我們需要配置多變量,我們可以使用args(name,age)來配置,對應對象方法Annotation_aspect.Record(String name,Integer age)和UserController.login(String name,Integer age)。 
5. 除了args(),this(),target(),@args(),@within(),@target()和@annotation等函數都可以指定參數名,來將目標連接點上的方法入參綁定到增強的方法中。

3. 目標類切點函數

函數 入參 說明 示例
within() 類名匹配串 定位於特定作用於下的所有連接點 within(com.yc.service.*ServiceImpl),可以通過此註解爲特定包下的所有以ServiceImpl名字結尾的類裏面的所有方法添加事務控制。
target() 類名 定位於指定類及其子類 target(com.yc.service.IUserService),則可定位到IUserService接口和它的實現類如UserServiceImpl
@within() 類型註解類名 定位與標註了特定註解的類及其實現類 @within(com.yc.controller.needRecord),比如我們可以在BaseController中標註@needRecord,則所有繼承了BaseController的UserController、ArticleController等等都會被定位
@target() 類型註解類名 定位於標註了特定註解的目標類裏所有方法 @target(com.yc.controller.needRecord),則可以在controller層中,爲我們需要日誌記錄的類標註@needRecord。
  1. within()定位連接點的最細粒度是到類,相對於execution()可定位連接點大到包,小到方法入參的適用範圍更窄。
  2. 相對於@within,顯然@target的耦合性更低,針對性更強。比如UserController,ArticleControoler,HomeController都需要繼承BaseController,如果我們在BaseController中標註@needRecord,則三個子Controller都會被@within定位到織入增強,但實際上我們不想讓HomeController織入增強,顯然分別在UserController和ArticleController中標註@needRecord,然後利用@target來織入增強才滿足要求。
  3. @within()如果標註在一個接口上,則不會匹配實現了該接口的子類。

4. 代理類切點函數

主要爲this(),大多數情況使用方法與target()相同,區別在通過引介增強引入新接口方法時,新的接口方法同樣會被this()定位,但target()則不會。


切點獨立命名

在前面的講解裏,我們都是直接把切點配置到切點表達式函數裏的,而這種切點叫做匿名切點。但假如我們有新的需求,要爲相同的切點配置多個增強,這就需要我們在多個增強中配置相同的切點。爲了提高重用性和降低維護成本,我們可以通過@Pointcut註解來單獨命名切點。 
和前面相同的例子,我們將註解切面Annotation_aspect改造成如下:

@Aspect
public class Annotation_aspect {

    @Pointcut("execution(public * test.aop2.*.*(..))")
    private void ClassInTest_aop2(){}//修飾爲private表示此切點只能在本類中使用,這裏的返回值和方法題沒有實際用途

    @Pointcut("args(String)")
    protected void MethodWithArgNames(){}//修飾爲protected表示此切點只能在本類及其子類中使用

    @After("ClassInTest_aop2() and MethodWithArgNames() ")
    public void Record(JoinPoint joinPoint) throws Throwable{
        System.out.println("日誌記錄:用戶在" + new SimpleDateFormat("yyyy-MM-dd hh:mm;ss").format(new Date()) + "調用了"+ joinPoint.getSignature().getDeclaringTypeName()+"方法" );
    }

}

運行測試函數,控制檯打印: 
I’m zenghao ,I’m logining 
日誌記錄:用戶在2016-03-21 11:33;59調用了test.aop2.UserController方法

本文轉自:https://yq.aliyun.com/articles/29136

發佈了8 篇原創文章 · 獲贊 17 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章