Spring—切點表達式


摘要: 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方法
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM