摘要: 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。 |
- within()定位連接點的最細粒度是到類,相對於execution()可定位連接點大到包,小到方法入參的適用範圍更窄。
- 相對於@within,顯然@target的耦合性更低,針對性更強。比如UserController,ArticleControoler,HomeController都需要繼承BaseController,如果我們在BaseController中標註@needRecord,則三個子Controller都會被@within定位到織入增強,但實際上我們不想讓HomeController織入增強,顯然分別在UserController和ArticleController中標註@needRecord,然後利用@target來織入增強才滿足要求。
- @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