轉自:https://javadoop.com/post/spring-aop-intro
Spring AOP 發(fā)展到現(xiàn)在出現(xiàn)的全部 3 種配置方式顶瞳。
由于 Spring 強大的向后兼容性,實際代碼中往往會出現(xiàn)很多配置混雜的情況殉摔,而且居然還能工作,本文希望幫助大家理清楚這些知識记焊。
AOP, AspectJ, Spring AOP
我們先來把它們的概念和關系說說清楚逸月。
AOP 要實現(xiàn)的是在我們原來寫的代碼的基礎上,進行一定的包裝遍膜,如在方法執(zhí)行前碗硬、方法返回后、方法拋出異常后等地方進行一定的攔截處理或者叫增強處理瓢颅。
AOP 的實現(xiàn)并不是因為 Java 提供了什么神奇的鉤子恩尾,可以把方法的幾個生命周期告訴我們,而是我們要實現(xiàn)一個代理挽懦,實際運行的實例其實是生成的代理類的實例翰意。
作為 Java 開發(fā)者,我們都很熟悉 AspectJ 這個詞信柿,甚至于我們提到 AOP 的時候冀偶,想到的往往就是 AspectJ,即使你可能不太懂它是怎么工作的渔嚷。這里进鸠,我們把 AspectJ 和 Spring AOP 做個簡單的對比:
Spring AOP:
它基于動態(tài)代理來實現(xiàn)。默認地形病,如果使用接口的客年,用 JDK 提供的動態(tài)代理實現(xiàn)霞幅,如果沒有接口,使用 CGLIB 實現(xiàn)量瓜。大家一定要明白背后的意思司恳,包括什么時候會不用 JDK 提供的動態(tài)代理,而用 CGLIB 實現(xiàn)绍傲。
Spring 3.2 以后扔傅,spring-core 直接就把 CGLIB 和 ASM 的源碼包括進來了,這也是為什么我們不需要顯式引入這兩個依賴
Spring 的 IOC 容器和 AOP 都很重要唧取,Spring AOP 需要依賴于 IOC 容器來管理吕粗。
如果你是 web 開發(fā)者浇衬,有些時候,你可能需要的是一個 Filter 或一個 Interceptor洽腺,而不一定是 AOP鹏往。
Spring AOP 只能作用于 Spring 容器中的 Bean淡诗,它是使用純粹的 Java 代碼實現(xiàn)的,只能作用于 bean 的方法伊履。
Spring 提供了 AspectJ 的支持韩容,后面我們會單獨介紹怎么使用,一般來說我們用純的 Spring AOP 就夠了唐瀑。
很多人會對比 Spring AOP 和 AspectJ 的性能群凶,Spring AOP 是基于代理實現(xiàn)的,在容器啟動的時候需要生成代理實例哄辣,在方法調用上也會增加棧的深度请梢,使得 Spring AOP 的性能不如 AspectJ 那么好。
AspectJ:
AspectJ 出身也是名門力穗,來自于 Eclipse 基金會毅弧,link:https://www.eclipse.org/aspectj
-
屬于靜態(tài)織入,它是通過修改代碼來實現(xiàn)的当窗,它的織入時機可以是:
- Compile-time weaving:編譯期織入够坐,如類 A 使用 AspectJ 添加了一個屬性,類 B 引用了它崖面,這個場景就需要編譯期的時候就進行織入元咙,否則沒法編譯類 B。
- Post-compile weaving:也就是已經生成了 .class 文件巫员,或已經打成 jar 包了蛾坯,這種情況我們需要增強處理的話,就要用到編譯后織入疏遏。
-
Load-time weaving:指的是在加載類的時候進行織入脉课,要實現(xiàn)這個時期的織入救军,有幾種常見的方法。1倘零、自定義類加載器來干這個唱遭,這個應該是最容易想到的辦法,在被織入類加載到 JVM 前去對它進行加載呈驶,這樣就可以在加載的時候定義行為了拷泽。2、在 JVM 啟動的時候指定 AspectJ 提供的 agent:
-javaagent:xxx/xxx/aspectjweaver.jar
袖瞻。
AspectJ 能干很多 Spring AOP 干不了的事情司致,它是 AOP 編程的完全解決方案。Spring AOP 致力于解決的是企業(yè)級開發(fā)中最普遍的 AOP 需求(方法織入)聋迎,而不是力求成為一個像 AspectJ 一樣的 AOP 編程完全解決方案脂矫。
因為 AspectJ 在實際代碼運行前完成了織入,所以大家會說它生成的類是沒有額外運行時開銷的霉晕。
AOP 術語解釋
在這里庭再,不準備解釋那么多 AOP 編程中的術語了,我們碰到一個說一個吧牺堰。
Advice拄轻、Advisor、Pointcut伟葫、Aspect恨搓、Joinpoint 等等。
Spring AOP
首先要說明的是筏养,這里介紹的 Spring AOP 是純的 Spring 代碼奶卓,和 AspectJ 沒什么關系,但是 Spring 延用了 AspectJ 中的概念撼玄,包括使用了 AspectJ 提供的 jar 包中的注解夺姑,但是不依賴于其實現(xiàn)功能。
后面介紹的如 @Aspect掌猛、@Pointcut盏浙、@Before、@After 等注解都是來自于 AspectJ荔茬,但是功能的實現(xiàn)是純 Spring AOP 自己實現(xiàn)的废膘。
下面我們來介紹 Spring AOP 的使用方法,先從最簡單的配置方式開始說起慕蔚,這樣讀者想看源碼也會比較容易丐黄。
目前 Spring AOP 一共有三種配置方式,Spring 做到了很好地向下兼容孔飒,所以大家可以放心使用灌闺。
Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于幾個接口的艰争,想看源碼的同學可以從這里起步。
Spring 2.0 @AspectJ 配置:使用注解的方式來配置桂对,這種方式感覺是最方便的甩卓,還有,這里雖然叫做 @AspectJ蕉斜,但是這個和 AspectJ 其實沒啥關系逾柿。
Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式來配置,使用 命名空間 <aop />
Spring 1.2 中的配置
這節(jié)我們將介紹 Spring 1.2 中的配置宅此,這是最古老的配置机错,但是由于 Spring 提供了很好的向后兼容,以及很多人根本不知道什么配置是什么版本的父腕,以及是否有更新更好的配置方法替代弱匪,所以還是會有很多代碼是采用這種古老的配置方式的,這里說的古老并沒有貶義的意思侣诵。
下面用一個簡單的例子來演示怎么使用 Spring 1.2 的配置方式痢法。
首先狱窘,我們先定義兩個接口 UserService
和 OrderService
杜顺,以及它們的實現(xiàn)類 UserServiceImpl
和 OrderServiceImpl
:
接下來,我們定義兩個
advice
蘸炸,分別用于攔截方法執(zhí)行前和方法返回后:
advice
是我們接觸的第一個概念躬络,記住它是干什么用的。
上面的兩個 Advice 分別用于方法調用前輸出參數和方法調用后輸出結果搭儒。
現(xiàn)在可以開始配置了穷当,我們配置一個名為 spring_1_2.xml 的文件:
接下來,我們跑起來看看:
查看輸出結果:
準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: queryUser, 參數列表:[]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
從結果可以看到淹禾,對 UserService 中的兩個方法都做了前馁菜、后攔截。這個例子理解起來應該非常簡單铃岔,就是一個代理實現(xiàn)汪疮。
代理模式需要一個接口、一個具體實現(xiàn)類毁习,然后就是定義一個代理類智嚷,用來包裝實現(xiàn)類,添加自定義邏輯纺且,在使用的時候盏道,需要用代理類來生成實例。
此中方法有個致命的問題载碌,如果我們需要攔截 OrderService 中的方法猜嘱,那么我們還需要定義一個 OrderService 的代理衅枫。如果還要攔截 PostService,得定義一個 PostService 的代理......
而且泉坐,我們看到为鳄,我們的攔截器的粒度只控制到了類級別,類中所有的方法都進行了攔截腕让。接下來孤钦,我們看看怎么樣只攔截特定的方法。
在上面的配置中纯丸,配置攔截器的時候偏形,interceptorNames 除了指定為 Advice,是還可以指定為 Interceptor 和 Advisor 的觉鼻。
這里我們來理解 Advisor 的概念俊扭,它也比較簡單,它內部需要指定一個 Advice坠陈,Advisor 決定該攔截哪些方法萨惑,攔截后需要完成的工作還是內部的 Advice 來做。
它有好幾個實現(xiàn)類仇矾,這里我們使用實現(xiàn)類 NameMatchMethodPointcutAdvisor 來演示庸蔼,從名字上就可以看出來,它需要我們給它提供方法名字贮匕,這樣符合該配置的方法才會做攔截姐仅。
我們可以看到,userServiceProxy 這個 bean 配置了一個 advisor刻盐,advisor 內部有一個 advice掏膏。advisor 負責匹配方法,內部的 advice 負責實現(xiàn)方法包裝敦锌。
注意馒疹,這里的 mappedNames 配置是可以指定多個的,用逗號分隔乙墙,可以是不同類中的方法颖变。相比直接指定 advice,advisor 實現(xiàn)了更細粒度的控制伶丐,因為在這里配置 advice 的話悼做,所有方法都會被攔截。
輸出結果如下哗魂,只有 createUser 方法被攔截:
準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
到這里肛走,我們已經了解了 Advice 和 Advisor 了,前面也說了還可以配置 Interceptor录别。
對于 Java 開發(fā)者來說朽色,對 Interceptor 這個概念肯定都很熟悉了邻吞,這里就不做演示了,貼一下實現(xiàn)代碼:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
// 執(zhí)行 真實實現(xiàn)類 的方法
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
上面葫男,我們介紹完了 Advice抱冷、Advisor、Interceptor 三個概念梢褐,相信大家應該很容易就看懂它們了旺遮。
它們有個共同的問題,那就是我們得為每個 bean 都配置一個代理盈咳,之后獲取 bean 的時候需要獲取這個代理類的 bean 實例(如 (UserService) context.getBean("userServiceProxy"))耿眉,這顯然非常不方便,不利于我們之后要使用的自動根據類型注入鱼响。下面介紹 autoproxy 的解決方案鸣剪。
autoproxy:從名字我們也可以看出來,它是實現(xiàn)自動代理丈积,也就是說當 Spring 發(fā)現(xiàn)一個 bean 需要被切面織入的時候筐骇,Spring 會自動生成這個 bean 的一個代理來攔截方法的執(zhí)行,確保定義的切面能被執(zhí)行江滨。
這里強調自動铛纬,也就是說 Spring 會自動做這件事,而不用像前面介紹的牙寞,我們需要顯式地指定代理類的 bean饺鹃。
我們去掉原來的 ProxyFactoryBean 的配置莫秆,改為使用 BeanNameAutoProxyCreator 來配置:
配置很簡單间雀,beanNames 中可以使用正則來匹配 bean 的名字。這樣配置出來以后镊屎,userServiceBeforeAdvice 和 userServiceAfterAdvice 這兩個攔截器就不僅僅可以作用于 UserServiceImpl 了惹挟,也可以作用于 OrderServiceImpl、PostServiceImpl缝驳、ArticleServiceImpl......等等连锯,也就是說不再是配置某個 bean 的代理了。
注意用狱,這里的 InterceptorNames 和前面一樣运怖,也是可以配置成 Advisor 和 Interceptor 的。
然后我們修改下使用的地方:
發(fā)現(xiàn)沒有夏伊,我們在使用的時候摇展,完全不需要關心代理了,直接使用原來的類型就可以了溺忧,這是非常方便的咏连。
輸出結果就是 OrderService 和 UserService 中的每個方法都得到了攔截:
準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: queryUser, 參數列表:[]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: createOrder, 參數列表:[Leo, 隨便買點什么]
方法返回:Order{username='Leo', product='隨便買點什么'}
準備執(zhí)行方法: queryOrder, 參數列表:[Leo]
方法返回:Order{username='Leo', product='隨便買點什么'}
到這里盯孙,是不是發(fā)現(xiàn) BeanNameAutoProxyCreator 非常好用,它需要指定被攔截類名的模式(如 *ServiceImpl)祟滴,它可以配置多次振惰,這樣就可以用來匹配不同模式的類了。
另外垄懂,在 BeanNameAutoProxyCreator 同一個包中骑晶,還有一個非常有用的類 DefaultAdvisorAutoProxyCreator,比上面的 BeanNameAutoProxyCreator 還要方便草慧。
之前我們說過透罢,advisor 內部包裝了 advice,advisor 負責決定攔截哪些方法冠蒋,內部 advice 定義攔截后的邏輯羽圃。所以,仔細想想其實就是只要讓我們的 advisor 全局生效就能實現(xiàn)我們需要的自定義攔截功能抖剿、攔截后的邏輯處理朽寞。
BeanNameAutoProxyCreator 是自己匹配方法,然后交由內部配置 advice 來攔截處理斩郎;
而 DefaultAdvisorAutoProxyCreator 是讓 ioc 容器中的所有 advisor 來匹配方法脑融,advisor 內部都是有 advice 的,讓它們內部的 advice 來執(zhí)行攔截處理缩宜。
1肘迎、我們需要再回頭看下 Advisor 的配置,上面我們用了 NameMatchMethodPointcutAdvisor 這個類:
<bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logArgsAdvice" />
<property name="mappedNames" value="createUser,createOrder" />
</bean>
其實 Advisor 還有一個更加靈活的實現(xiàn)類 RegexpMethodPointcutAdvisor锻煌,它能實現(xiàn)正則匹配妓布,如:
<bean id="logArgsAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logArgsAdvice" />
<property name="pattern" value="com.javadoop.*.service.*.create.*" />
</bean>
也就是說,我們能通過配置 Advisor宋梧,精確定位到需要被攔截的方法匣沼,然后使用內部的 Advice 執(zhí)行邏輯處理。
2捂龄、之后释涛,我們需要配置 DefaultAdvisorAutoProxyCreator,它的配置非常簡單倦沧,直接使用下面這段配置就可以了唇撬,它就會使得所有的 Advisor 自動生效,無須其他配置展融。
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
然后我們運行一下:
輸出:
準備執(zhí)行方法: createUser, 參數列表:[Tom, Cruise, 55]
方法返回:User{firstName='Tom', lastName='Cruise', age=55, address='null'}
準備執(zhí)行方法: createOrder, 參數列表:[Leo, 隨便買點什么]
方法返回:Order{username='Leo', product='隨便買點什么'}
從結果可以看出窖认,create 方法使用了 logArgsAdvisor 進行傳參輸出,query 方法使用了 logResultAdvisor 進行了返回結果輸出。
到這里耀态,Spring 1.2 的配置就要介紹完了轮傍。本文不會介紹得面面俱到,主要是關注最核心的配置首装,如果讀者感興趣创夜,要學會自己去摸索,比如這里的 Advisor 就不只有我這里介紹的 NameMatchMethodPointcutAdvisor 和 RegexpMethodPointcutAdvisor仙逻,AutoProxyCreator 也不僅僅是 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator驰吓。
讀到這里,我想對于很多人來說系奉,就知道怎么去閱讀 Spring AOP 源碼了檬贰。
Spring 2.0 @AspectJ 配置
Spring 2.0 以后,引入了 @AspectJ 和 Schema-based 的兩種配置方式缺亮,我們先來介紹 @AspectJ 的配置方式翁涤,之后我們再來看使用 xml 的配置方式。
注意了萌踱,@AspectJ 和 AspectJ 沒多大關系葵礼,并不是說基于 AspectJ 實現(xiàn)的,而僅僅是使用了 AspectJ 中的概念并鸵,包括使用的注解也是直接來自于 AspectJ 的包鸳粉。
首先,我們需要依賴 aspectjweaver.jar 這個包园担,這個包來自于 AspectJ:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.11</version>
</dependency>
如果是使用 Spring Boot 的話届谈,添加以下依賴即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因為我們需要使用 AspectJ 的處理功能弯汰,而是因為 Spring 使用了 AspectJ 提供的一些注解艰山,實際上還是純的 Spring AOP 代碼。
說了這么多蝙泼,明確一點程剥,@AspectJ 采用注解的方式來配置使用 Spring AOP劝枣。
首先汤踏,我們需要開啟 @AspectJ 的注解配置方式,有兩種方式:
1舔腾、在 xml 中配置:
<aop:aspectj-autoproxy/>
2溪胶、使用 @EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
一旦開啟了上面的配置,那么所有使用 @Aspect 注解的 bean 都會被 Spring 當做用來實現(xiàn) AOP 的配置類稳诚,我們稱之為一個 Aspect哗脖。
注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式才避,還是在 xml 中配置 bean橱夭,首先它需要是一個 bean。
比如下面這個 bean桑逝,它的類名上使用了 @Aspect棘劣,它就會被當做 Spring AOP 的配置。
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
接下來楞遏,我們需要關心的是 @Aspect 注解的 bean 中茬暇,我們需要配置哪些內容。
首先寡喝,我們需要配置 Pointcut糙俗,Pointcut 在大部分地方被翻譯成切點,用于定義哪些方法需要被增強或者說需要被攔截预鬓,有點類似于之前介紹的 Advisor 的方法匹配巧骚。
Spring AOP 只支持 bean 中的方法(不像 AspectJ 那么強大),所以我們可以認為 Pointcut 就是用來匹配 Spring 容器中的所有 bean 的方法的格二。
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
我們看到网缝,@Pointcut 中使用了 execution 來正則匹配方法簽名,這也是最常用的蟋定,除了 execution粉臊,我們再看看其他的幾個比較常用的匹配方式:
- within:指定所在類或所在包下面的方法(Spring AOP 獨有)
如 @Pointcut("within(com.javadoop.springaoplearning.service..*)")
- @annotation:方法上具有特定的注解,如 @Subscribe 用于訂閱特定的事件驶兜。
如 @Pointcut("execution( .*(..)) && @annotation(com.javadoop.annotation.Subscribe)")
- bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 獨有)
如 @Pointcut("bean(*Service)")
Tips:上面匹配中扼仲,通常 "." 代表一個包名,".." 代表包及其子包抄淑,方法參數任意匹配使用兩個點 ".."屠凶。
對于 web 開發(fā)者,Spring 有個很好的建議肆资,就是定義一個 SystemArchitecture:
@Aspect
public class SystemArchitecture {
// web 層
@Pointcut("within(com.javadoop.web..*)")
public void inWebLayer() {}
// service 層
@Pointcut("within(com.javadoop.service..*)")
public void inServiceLayer() {}
// dao 層
@Pointcut("within(com.javadoop.dao..*)")
public void inDataAccessLayer() {}
// service 實現(xiàn)矗愧,注意這里指的是方法實現(xiàn),其實通常也可以使用 bean(*ServiceImpl)
@Pointcut("execution(* com.javadoop..service.*.*(..))")
public void businessService() {}
// dao 實現(xiàn)
@Pointcut("execution(* com.javadoop.dao.*.*(..))")
public void dataAccessOperation() {}
}
上面這個 SystemArchitecture 很好理解郑原,該 Aspect 定義了一堆的 Pointcut唉韭,隨后在任何需要 Pointcut 的地方都可以直接引用(如 xml 中的 pointcut-ref="")。
配置 pointcut 就是配置我們需要攔截哪些方法犯犁,接下來属愤,我們要配置需要對這些被攔截的方法做什么,也就是前面介紹的 Advice酸役。
接下來住诸,我們要配置 Advice驾胆。
下面這塊代碼示例了各種常用的情況:
注意,實際寫代碼的時候贱呐,不要把所有的切面都揉在一個 class 中丧诺。
@Aspect
public class AdviceExample {
// 這里會用到我們前面說的 SystemArchitecture
// 下面方法就是寫攔截 "dao層實現(xiàn)"
@Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ... 實現(xiàn)代碼
}
// 當然,我們也可以直接"內聯(lián)"Pointcut奄薇,直接在這里定義 Pointcut
// 把 Advice 和 Pointcut 合在一起了锅必,但是這兩個概念我們還是要區(qū)分清楚的
@Before("execution(* com.javadoop.dao.*.*(..))")
public void doAccessCheck() {
// ... 實現(xiàn)代碼
}
@AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning(
pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// 這樣,進來這個方法的處理時候惕艳,retVal 就是相應方法的返回值搞隐,是不是非常方便
// ... 實現(xiàn)代碼
}
// 異常返回
@AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ... 實現(xiàn)代碼
}
@AfterThrowing(
pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ... 實現(xiàn)代碼
}
// 注意理解它和 @AfterReturning 之間的區(qū)別,這里會攔截正常返回和異常的情況
@After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// 通常就像 finally 塊一樣使用远搪,用來釋放資源劣纲。
// 無論正常返回還是異常退出,都會被攔截到
}
// 感覺這個很有用吧谁鳍,既能做 @Before 的事情癞季,也可以做 @AfterReturning 的事情
@Around("com.javadoop.aop.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
細心的讀者可能發(fā)現(xiàn)了有些 Advice 缺少方法傳參,如在 @Before 場景中參數往往是非常有用的倘潜,比如我們要用日志記錄下來被攔截方法的入參情況绷柒。
Spring 提供了非常簡單的獲取入參的方法,使用 org.aspectj.lang.JoinPoint 作為 Advice 的第一個參數即可涮因,如:
@Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()")
public void logArgs(JoinPoint joinPoint) {
System.out.println("方法執(zhí)行前废睦,打印入參:" + Arrays.toString(joinPoint.getArgs()));
}
注意:第一,必須放置在第一個參數上养泡;第二嗜湃,如果是 @Around,我們通常會使用其子類 ProceedingJoinPoint澜掩,因為它有 procceed()/procceed(args[]) 方法购披。
到這里,我們介紹完了 @AspectJ 配置方式中的 Pointcut 和 Advice 的配置肩榕。對于開發(fā)者來說刚陡,其實最重要的就是這兩個了,定義 Pointcut 和使用合適的 Advice 在各個 Pointcut 上株汉。
下面筐乳,我們用這一節(jié)介紹的 @AspectJ 來實現(xiàn)上一節(jié)實現(xiàn)的記錄方法傳參和記錄方法返回值。
xml 的配置非常簡單:
這里是示例郎逃,所以 bean 的配置還是使用了 xml 的配置方式哥童。
測試一下:
輸出結果:
方法執(zhí)行前,打印入參:[Tom, Cruise, 55]
User{firstName='Tom', lastName='Cruise', age=55, address='null'}
方法執(zhí)行前褒翰,打印入參:[]
User{firstName='Tom', lastName='Cruise', age=55, address='null'}
JoinPoint 除了 getArgs() 外還有一些有用的方法,大家可以進去稍微看一眼。
最后提一點优训,@Aspect 中的配置不會作用于使用 @Aspect 注解的 bean朵你。
Spring 2.0 schema-based 配置
本節(jié)將介紹的是 Spring 2.0 以后提供的基于 <aop /> 命名空間的 XML 配置。這里說的 schema-based 就是指基于 aop 這個 schema揣非。
介紹 IOC 的時候也介紹過 Spring 是怎么解析各個命名空間的(各種 *NamespaceHandler)抡医,解析 <aop /> 的源碼在 org.springframework.aop.config.AopNamespaceHandler 中。
有了前面的 @AspectJ 的配置方式的知識早敬,理解 xml 方式的配置非常簡單忌傻,所以我們就可以廢話少一點了。
這里先介紹配置 Aspect搞监,便于后續(xù)理解:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
所有的配置都在 <aop:config> 下面水孩。
<aop:aspect > 中需要指定一個 bean,和前面介紹的 LogArgsAspect 和 LogResultAspect 一樣琐驴,我們知道該 bean 中我們需要寫處理代碼俘种。
然后,我們寫好 Aspect 代碼后绝淡,將其“織入”到合適的 Pointcut 中宙刘,這就是面向切面。
然后牢酵,我們需要配置 Pointcut悬包,非常簡單,如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.javadoop.springaoplearning.service.*.*(..))"/>
<!--也可以像下面這樣-->
<aop:pointcut id="businessService2"
expression="com.javadoop.SystemArchitecture.businessService()"/>
</aop:config>
將 <aop:pointcut> 作為 <aop:config> 的直接子元素馍乙,將作為全局 Pointcut玉罐。
我們也可以在 <aop:aspect />內部配置 Pointcut,這樣該 Pointcut 僅用于該 Aspect:
<aop:config>
<aop:aspect ref="logArgsAspect">
<aop:pointcut id="internalPointcut"
expression="com.javadoop.SystemArchitecture.businessService()" />
</aop:aspect>
</aop:config>
接下來潘拨,我們應該配置 Advice 了吊输,為了避免廢話過多,我們直接上實例吧铁追,非常好理解季蚂,將上一節(jié)用 @AspectJ 方式配置的搬過來:
上面的例子中,我們配置了兩個 LogArgsAspect 和一個 LogResultAspect琅束。
其實基于 XML 的配置也是非常靈活的扭屁,這里沒辦法給大家演示各種搭配,大家抓住基本的 Pointcut涩禀、Advice 和 Aspect 這幾個概念料滥,就很容易配置了。
小結
到這里艾船,本文介紹了 Spring AOP 的三種配置方式葵腹,我們要知道的是高每,到目前為止,我們使用的都是 Spring AOP践宴,和 AspectJ 沒什么關系鲸匿。
下一篇文章,將會介紹 AspectJ 的使用方式阻肩,以及怎樣在 Spring 應用中使用 AspectJ带欢。之后差不多就可以出 Spring AOP 源碼分析了。