Spring 的增強(qiáng)提供了連接點(diǎn)的方位信息(織入方法前、方法后等),而切點(diǎn)則描述了增強(qiáng)需要織入到哪個(gè)類的哪一個(gè)方法上。

Spring 通過 Pointcut 來描述切點(diǎn),它是由 ClassFilter 和 MethodMatcher 組成的。ClassFilter 用于定位到特定的類,MethodMatcher 用于定位到特定的方法上。
ClassFilter 自定義了一個(gè)方法 matches(Class<?> clazz),clazz 表示被檢查的類,這個(gè)方法用來判斷是否是條件所要求的類。
Spring 支持兩種方法匹配器:
- 靜態(tài)方法匹配器 - 僅對(duì)方法簽名(方法名、入?yún)㈩愋团c順序)進(jìn)行匹配;僅判別一次。
- 動(dòng)態(tài)方法匹配器 - 在運(yùn)行期檢查方法入?yún)⒌闹?。每次調(diào)用方法都會(huì)進(jìn)行判別,因此對(duì)性能有很大影響。
使用哪一種的方法匹配器是由 MethodMatcher 的 isRuntime() 方法的返回值決定的,true 表示使用動(dòng)態(tài)方法匹配器。
從 Spring2.0+ 開始,支持注解切點(diǎn)(JDK5.0 +)與字符串表達(dá)式切點(diǎn),它們使用的都是 AspectJ 切點(diǎn)表達(dá)式語言。
1 切點(diǎn)類型
Spring 提供 6 種類型的切點(diǎn):
| 切點(diǎn)類型 | 說明 |
|---|---|
| 靜態(tài)方法切點(diǎn) |
org.springframework.aop.support.StaticMethodMatcherPointcut 是靜態(tài)方法切點(diǎn)的抽象基類,默認(rèn)情況下它匹配所有的類 。 StaticMethodMatcherPointcut 包括兩個(gè)主要的子類,它們是 NameMatchMethodPointcut 和 AbstractRegexpMethodPointcut ,前者提供簡單字符串匹配方法簽名,而后者是使用正則表達(dá)式匹配方法簽名。 |
| 動(dòng)態(tài)方法切點(diǎn) |
org.springframework.aop.support.DynamicMethodMatcherPointcut 是動(dòng)態(tài)方法切點(diǎn)的抽象基類,默認(rèn)情況下它匹配所有的類 。 |
| 注解切點(diǎn) |
org.springframework.aop.support.annotation.AnnotationMatchingPointcut 實(shí)現(xiàn)類表示注解切點(diǎn) 。 使用 AnnotationMatchingPointcut 支持在 Bean 中直接通過 JDK 5.0 注解標(biāo)簽來定義切點(diǎn)。 |
| 表達(dá)式切點(diǎn) | 使用 org.springframework.aop.support.ExpressionPointcut 接口來支持 AspectJ 切點(diǎn)表達(dá)式語法。 |
| 流程切點(diǎn) | 由org.springframework.aop.support.ControlFlowPointcut 實(shí)現(xiàn)類來表示控制流程切點(diǎn) 。ControlFlowPointcut 是一種特殊的切點(diǎn),它回根據(jù)程序執(zhí)行堆棧的信息查找目標(biāo)方法是否由某一個(gè)方法直接或間接發(fā)起的調(diào)用,以此判斷是否為匹配的連接點(diǎn)。 |
| 復(fù)合切點(diǎn) |
org.springframework.aop.support.ComposablePointcut 實(shí)現(xiàn)類是為創(chuàng)建多個(gè)切點(diǎn)而提供的操作類 。 它的所有方法都返回 ComposablePointcut 類,這樣,我們就可以使用鏈接表達(dá)式對(duì)其進(jìn)行操作啦O(∩_∩)O哈哈~ |
2 切面類型
由于增強(qiáng)既包含橫切代碼,又包含部分的連接點(diǎn)信息(方法前 、 方法后等的方位信息),所以我們可以僅通過增強(qiáng)類來生成一個(gè)切面 。 但切點(diǎn)僅代表目標(biāo)類連接點(diǎn)的部分信息(即定位到哪個(gè)類的哪個(gè)方法),所以切點(diǎn)必須結(jié)合增強(qiáng)才能制作出一個(gè)切面 。 Spring 使用 org.springframework.aop.Advisor 接口表示切面,一個(gè)切面同時(shí)包含橫切代碼和連接點(diǎn)信息 。

切面可分為以下三類:
| 切面類型 | 說明 |
|---|---|
| Advisor | 一般切面,它僅包含一個(gè) Advice ,因?yàn)?Advice 包含了橫切代碼和連接點(diǎn)的信息,所以 Advice 本身就是一個(gè)簡單的切面,只是它代表的橫切連接點(diǎn)是所有目標(biāo)類的所有方法,這個(gè)橫切面太寬泛,所以在實(shí)踐中一般不會(huì)直接使用。 |
| PointcutAdvisor | 具有切點(diǎn)的切面,它包含 Advice 和 Pointcut 兩個(gè)類,這樣我們就可以通過類 、 方法名以及方法方位等信息靈活地定義出切面的連接點(diǎn)信息,從而提供更具適用性的切面。 |
| IntroductionAdvisor | 引介切面。它是對(duì)應(yīng)引介增強(qiáng)的特殊的切面,應(yīng)用于類,所以引介切點(diǎn)是使用 ClassFilter 來定義的。 |

PointcutAdvisor 有 6 個(gè)具體的實(shí)現(xiàn)類:
| 實(shí)現(xiàn)類 | 說明 |
|---|---|
| DefaultPointcutAdvisor | 最常用的切面類型,通過它可以設(shè)定任意的 Pointcut 和 Advice 定義一個(gè)切面,唯一不支持的是引介的切面類型,可以通過擴(kuò)展該類實(shí)現(xiàn)自定義切面。 |
| NameMatchMethodPointcutAdvisor | 實(shí)現(xiàn)按方法名來定義切點(diǎn)的切面。 |
| RegexpMethodPointcutAdvisor | 使用正則表達(dá)式來匹配方法名,實(shí)現(xiàn)切點(diǎn)定義的切面 。 內(nèi)部是通過 JdkRegexpMethodPointcut 類來構(gòu)建出正則表達(dá)式方法名的切點(diǎn)的 。 |
| StaticMethodMatcherPointcutAdvisor | 使用靜態(tài)方法匹配器來定義切點(diǎn)的切面。默認(rèn)情況下,匹配所有的目標(biāo)類。 |
| AspecJExpressionPointcutAdvisor | 使用 Aspecj 切點(diǎn)表達(dá)式來定義切點(diǎn)的切面 。 |
| AspecJPointcutAdvisor | 使用 AspecJ 語法來定義切點(diǎn)的切面 。 |
這些 Advisor 的實(shí)現(xiàn)類都是通過擴(kuò)展對(duì)應(yīng) Pointcut 實(shí)現(xiàn)類并實(shí)現(xiàn) PointcutAdvisor 接口來進(jìn)行定義 。 此外, Advisor 都實(shí)現(xiàn)了 org.springframework.core.Ordered 接口, Spring 會(huì)根據(jù) Advisor 定義的順序來決定織入切面的順序 。
3 靜態(tài)方法名匹配
StaticMethodMatcherPointcutAdvisor 代表靜態(tài)方法匹配切面,它通過 StaticMethodMatcherPointcut 來定義切點(diǎn),并通過類和方法名來匹配所定義的切點(diǎn)。
假設(shè)有兩個(gè)業(yè)務(wù)類 User 與 Charger,它們都定義了相同的方法。
User 類:
public class User {
public void rent(String userId) {
System.out.println("User:租賃【充電寶】");
}
}
Charger 類:
public class Charger {
public void rent(String userId) {
System.out.println("Charger:【充電寶】被租賃");
}
}
我們希望對(duì) User 的 rent(String userId) 方法實(shí)施前置增強(qiáng)。
切面類:
public class RentAdvisor extends StaticMethodMatcherPointcutAdvisor {
/**
* 設(shè)定切點(diǎn)方法的匹配規(guī)則
*
* @param method
* @param targetClass
* @return
*/
public boolean matches(Method method, Class<?> targetClass) {
return "rent".equals(method.getName());//方法名為 rent
}
/**
* 設(shè)定切點(diǎn)類的匹配規(guī)則
*
* @return
*/
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class<?> clazz) {
return User.class.isAssignableFrom(clazz);//是 User 的類或子類
}
};
}
}
要使用切面,我們還需要定義一個(gè)增強(qiáng):
public class RentBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object o) throws Throwable {
System.out.println("準(zhǔn)備租賃的用戶 ID:" + args[0]);
}
}
配置切面:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="user" class="net.deniro.spring4.aop.User"/>
<bean id="charger" class="net.deniro.spring4.aop.Charger"/>
<!-- 前置增強(qiáng)-->
<bean id="rentBeforeAdvice" class="net.deniro.spring4.aop.RentBeforeAdvice"/>
<!-- 切面-->
<bean id="rentAdvisor" class="net.deniro.spring4.aop.RentAdvisor"
p:advice-ref="rentBeforeAdvice"/>
<!-- 通過父 Bean 來定義公共的配置信息-->
<bean id="parentBean" abstract="true"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="rentAdvisor"
p:proxyTargetClass="true"/>
<!-- 代理-->
<bean id="userProxy" parent="parentBean" p:target-ref="user"/>
<bean id="chargerProxy" parent="parentBean" p:target-ref="charger"/>
</beans>
使用切面 rentAdvisor 的 advice-ref 屬性來指定前置增強(qiáng)。
StaticMethodMatcherPointcutAdvisor 除了 advice 屬性之外還有兩個(gè)屬性:
| 屬性 | 說明 |
|---|---|
| classFilter | 類匹配過濾器。(在 RentAdvisor 中采用編碼方式實(shí)現(xiàn)了這個(gè)過濾器) |
| order | 切面織入的順序。 |
這里還需要了一個(gè)父 Bean 來簡化配置。
單元測試:
User user = (User) context.getBean("userProxy");
Charger charger = (Charger) context.getBean("chargerProxy");
String userId="001";
user.rent(userId);
charger.rent(userId);
輸出結(jié)果:
準(zhǔn)備租賃的用戶 ID:001
User:租賃【充電寶】
Charger:【充電寶】被租賃
從輸出結(jié)果中可以看出, User 方法被織入增強(qiáng)。
4 靜態(tài)正則表達(dá)式方法名匹配
StaticMethodMatcherPointcutAdvisor 僅能通過方法名來定義切點(diǎn),這種方式不夠靈活,如果目標(biāo)類中包含多個(gè)方法,而且它們滿足一定的命名規(guī)范,那么使用正則表達(dá)式就很方便 。
配置:
<!-- 靜態(tài)正則表達(dá)式方法名匹配-->
<bean id="regexpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="rentBeforeAdvice">
<!-- 匹配模式-->
<property name="patterns">
<list>
<!-- 匹配字符串-->
<value>.*rent.*</value>
</list>
</property>
</bean>
<!-- 代理-->
<bean id="userProxy2"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="rentAdvisor"
p:target-ref="user"
p:proxyTargetClass="true"/>
單元測試:
User user = (User) context.getBean("userProxy2");
String userId="002";
user.rent(userId);
輸出結(jié)果:
準(zhǔn)備租賃的用戶 ID:002
User:租賃【充電寶】
RegexpMethodPointcutAdvisor 除了 pattern 和 advice 屬性之外 ,還有另外兩個(gè)屬性:
| 屬性 | 說明 |
|---|---|
| pattern | 只定義一個(gè)匹配模式串。 |
| paterns | 定義多個(gè)匹配模式串;這些匹配模式串之間是 “ 或 ” 的關(guān)系 。 |
| order | 切面織入時(shí)對(duì)應(yīng)的順序。 |
正則表達(dá)式語法請(qǐng)參見 說說正則表達(dá)式的基礎(chǔ)語法。
只要應(yīng)用類包具有良好的命名規(guī)范,那么就可以使用簡單的正則表達(dá)式來描述目標(biāo)方法。好的命名規(guī)范既可以增強(qiáng)程序的可讀性與團(tuán)隊(duì)開發(fā)的協(xié)作性,有能夠降低溝通成本,所以是值得推廣的好的編程實(shí)踐方法。
推薦一款正則表達(dá)式工具 RegexBuddy,它是一款全球知名的正則式測試工具,支持多平臺(tái)規(guī)則測試,支持正則式分組測試以及對(duì)相關(guān)字符進(jìn)行高亮顯示。具體請(qǐng)參見 正則表達(dá)式工具 RegexBuddy 使用指南。
5 動(dòng)態(tài)切面
可以使用 DefaultPointcutAdvisor 和 DynamicMethodMatcherPointcut 來創(chuàng)建動(dòng)態(tài)切面。
DynamicMethodMatcherPointcut 是一個(gè)抽象類,它繼承的抽象類 DynamicMethodMatcher 將 isRuntime() 標(biāo)識(shí)為 final 并返回 true ,這樣其子類就一定是一個(gè)動(dòng)態(tài)切點(diǎn) 。 DynamicMethodMatcherPointcut 默認(rèn)匹配所有的類和方法,所以需要擴(kuò)展該類以編寫出符合要求的動(dòng)態(tài)切點(diǎn):
public class RentDynamicPointcut extends DynamicMethodMatcherPointcut {
private static List<String> SPECIAL_USER_IDS = new ArrayList();
static {
SPECIAL_USER_IDS.add("001");
}
/**
* 對(duì)類做靜態(tài)切點(diǎn)檢查
*
* @return
*/
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class<?> clazz) {
System.out.println("對(duì) " + clazz.getName() + " 做靜態(tài)切點(diǎn)檢查。");
return User.class.isAssignableFrom(clazz);
}
};
}
/**
* 對(duì)方法進(jìn)行靜態(tài)切點(diǎn)檢查
*
* @param method
* @param targetClass
* @return
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
System.out.println("對(duì) " + targetClass.getName() + " 的 " + method.getName() + " " +
"做靜態(tài)切點(diǎn)檢查。");
return "rent".equals(method.getName());
}
/**
* 對(duì)方法進(jìn)行動(dòng)態(tài)切點(diǎn)檢查
* @param method
* @param targetClass
* @param args
* @return
*/
public boolean matches(Method method, Class<?> targetClass, Object... args) {
System.out.println("對(duì) " + targetClass.getName() + " 的 " + method.getName() + " " +
"做動(dòng)態(tài)切點(diǎn)檢查。");
String userId = (String) args[0];
return SPECIAL_USER_IDS.contains(userId);
}
}
RentDynamicPointcut 類既有對(duì)方法進(jìn)行靜態(tài)切點(diǎn)檢查,又有對(duì)方法進(jìn)行動(dòng)態(tài)切點(diǎn)檢查 。
由于動(dòng)態(tài)切點(diǎn)檢查會(huì)對(duì)性能造成很大的影響,所以應(yīng)當(dāng)盡量避免在運(yùn)行時(shí)每次都對(duì)目標(biāo)類的各個(gè)方法進(jìn)行動(dòng)態(tài)檢查 。
Spring 在創(chuàng)建代理時(shí)對(duì)目標(biāo)類的每個(gè)連接點(diǎn)都使用靜態(tài)切點(diǎn)檢查,如果僅通過靜態(tài)切點(diǎn)檢查發(fā)現(xiàn)連接點(diǎn)不匹配,那么在運(yùn)行時(shí)就不再進(jìn)行動(dòng)態(tài)切點(diǎn)檢查 ;如果在靜態(tài)切點(diǎn)檢查發(fā)現(xiàn)連接點(diǎn)匹配,則再進(jìn)行動(dòng)態(tài)切點(diǎn)檢查。
配置:
<!-- 動(dòng)態(tài)切面-->
<bean id="dynamicAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="net.deniro.spring4.aop.RentDynamicPointcut"/>
</property>
<property name="advice">
<bean class="net.deniro.spring4.aop.RentBeforeAdvice"/>
</property>
</bean>
<!-- 代理-->
<bean id="userProxy3"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="dynamicAdvisor"
p:target-ref="user"
p:proxyTargetClass="true"/>
單元測試:
User user = (User) context.getBean("userProxy3");
String userId="001";
user.rent(userId);
userId="002";
user.rent(userId);
輸出結(jié)果:
對(duì) net.deniro.spring4.aop.User 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 的 rent 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 的 toString 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 的 clone 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 的 rent 做靜態(tài)切點(diǎn)檢查。
對(duì) net.deniro.spring4.aop.User 的 rent 做動(dòng)態(tài)切點(diǎn)檢查。
準(zhǔn)備租賃的用戶 ID:001
User:租賃【充電寶】
對(duì) net.deniro.spring4.aop.User 的 rent 做動(dòng)態(tài)切點(diǎn)檢查。
User:租賃【充電寶】
從輸出中可以看出:
- Spring 會(huì)對(duì)代理類的每一個(gè)方法執(zhí)行靜態(tài)切點(diǎn)檢查,如果這次檢查排除了某些方法,則下一次就不會(huì)再執(zhí)行切點(diǎn)檢查(靜態(tài)或動(dòng)態(tài));對(duì)于那些靜態(tài)切點(diǎn)檢查匹配了的方法,后續(xù)的調(diào)用都會(huì)執(zhí)行動(dòng)態(tài)切點(diǎn)檢查。
- 每次調(diào)用都會(huì)執(zhí)行動(dòng)態(tài)切點(diǎn)檢查,這對(duì)性能有很大影響。所以在定義切點(diǎn)時(shí),請(qǐng)先定義靜態(tài)切點(diǎn)檢查,即同時(shí)覆蓋
getClassFilter()與matches(Method method, Class<?> targetClass)方法,排除絕大多數(shù)的方法哦O(∩_∩)O哈哈~
Spring 的靜態(tài)切面指的是:在生成代理對(duì)象時(shí),就確定了是否把增強(qiáng)織入目標(biāo)類的連接點(diǎn);而動(dòng)態(tài)切面指的是:在運(yùn)行期根據(jù)方法入?yún)⒌闹?,來確定增強(qiáng)是否織入目標(biāo)類的連接點(diǎn)。這兩種切面都是通過動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)的。
6 流程切面
Spring 的流程切面是由 DefaultPointcutAdvisor 和 ControlFlowPointcut 實(shí)現(xiàn)的 。流程切點(diǎn)指的是由某個(gè)方法直接或者間接發(fā)起調(diào)用的其他方法。
假設(shè)我們希望通過一個(gè) UserDelegate 類的某個(gè)方法調(diào)用 User 中的 rent() 方法與 back() 方法:
public class UserDelegate {
private User user;
public void service(String userId) {
user.rent(userId);
user.back(userId);
}
public void setUser(User user) {
this.user = user;
}
}
現(xiàn)在使用流程切面,讓 UserDelegate.service 方法內(nèi)部調(diào)用的其他類方法都織入增強(qiáng):
<!-- 流程切點(diǎn)-->
<bean id="controlFlowPointcut"
class="org.springframework.aop.support.ControlFlowPointcut">
<!-- 指定類-->
<constructor-arg type="java.lang.Class" value="net.deniro.spring4.aop.UserDelegate"/>
<!-- 指定方法-->
<constructor-arg type="java.lang.String" value="service"/>
</bean>
<!-- 流程切面-->
<bean id="controlFlowAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:pointcut-ref="controlFlowPointcut"
p:advice-ref="rentBeforeAdvice"/>
<!-- 代理類-->
<bean id="userProxy4"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="controlFlowAdvisor"
p:target-ref="user"
p:proxyTargetClass="true"/>
ControlFlowPointcut 有兩個(gè)構(gòu)造函數(shù):
| 構(gòu)造函數(shù) | 說明 |
|---|---|
ControlFlowPointcut(Class<?> clazz) |
指定一個(gè)類作為流程切點(diǎn)。 |
ControlFlowPointcut(Class<?> clazz, String methodName) |
指定一個(gè)類和一個(gè)方法作為流程切點(diǎn)。 |
單元測試:
User user = (User) context.getBean("userProxy4");
System.out.println("增強(qiáng)前-------------");
String userId = "001";
user.rent(userId);
user.back(userId);
System.out.println("增強(qiáng)后-------------");
UserDelegate delegate = new UserDelegate();
delegate.setUser(user);
delegate.service(userId);
輸出結(jié)果:
增強(qiáng)前-------------
User:租賃【充電寶】
User:歸還【充電寶】
增強(qiáng)后-------------
準(zhǔn)備租賃的用戶 ID:001
User:租賃【充電寶】
準(zhǔn)備租賃的用戶 ID:001
User:歸還【充電寶】
流程切面和動(dòng)態(tài)切面都需要在運(yùn)行期進(jìn)行動(dòng)態(tài)判斷 。 于流程切面的代理對(duì)象在每次調(diào)用目標(biāo)類方法時(shí),都需要判斷方法調(diào)用堆棧中是否有符合流程切點(diǎn)要求的方法,所以它對(duì)性能的影響也很大。
7 復(fù)合切點(diǎn)切面
有時(shí)候,一個(gè)切點(diǎn)可能難以描述出目標(biāo)連接點(diǎn)的信息。比如之前流程切點(diǎn)的例子中,我們希望 在 UserDelegate#service() 方法發(fā)起的調(diào)用并且被調(diào)用的方法是 User#rent() 方法時(shí),才織入增強(qiáng),那么這個(gè)切點(diǎn)就是復(fù)合切點(diǎn),因?yàn)樗怯蓛蓚€(gè)切點(diǎn)共同確定的 。
Spring 的 ComposablePointcut 可以將多個(gè)切點(diǎn)以并集或者交集的方式組合起來,從而提供切點(diǎn)之間的復(fù)合運(yùn)算功能 。
ComposablePointcut 實(shí)現(xiàn)了 Pointcut 接口,所以它本身也是一個(gè)切點(diǎn),它有以下這些構(gòu)造函數(shù):
| 構(gòu)造函數(shù) | 說明 |
|---|---|
| ComposablePointcut() | 匹配所有類所有方法。 |
| ComposablePointcut(Pointcut pointcut) | 匹配特定切點(diǎn)。 |
| ComposablePointcut(ClassFilter classFilter) | 匹配特定類所有方法。 |
| ComposablePointcut(MethodMatcher methodMatcher) | 匹配所有類特定方法。 |
| ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher) | 匹配特定類特定方法。 |
ComposablePointcut 提供了 3 個(gè)交集運(yùn)算方法:
| 方法 | 說明 |
|---|---|
| ComposablePointcut intersection(ClassFilter other) | 復(fù)合切點(diǎn)和一個(gè) ClassFilter 對(duì)象進(jìn)行交集運(yùn)算。 |
| ComposablePointcut intersection(MethodMatcher other) | 復(fù)合切點(diǎn)和一個(gè) MethodMatcher 對(duì)象進(jìn)行交集運(yùn)算。 |
| ComposablePointcut intersection(Pointcut other) | 復(fù)合切點(diǎn)和一個(gè)切點(diǎn)對(duì)象進(jìn)行交集運(yùn)算。 |
ComposablePointcut 還提供了 3 個(gè)并集運(yùn)算方法:
| 方法 | 說明 |
|---|---|
| ComposablePointcut union(ClassFilter other) | 復(fù)合切點(diǎn)和一個(gè) ClassFilter 對(duì)象進(jìn)行并集運(yùn)算。 |
| ComposablePointcut union(MethodMatcher other) | 復(fù)合切點(diǎn)和一個(gè) MethodMatcher 對(duì)象進(jìn)行并集運(yùn)算。 |
| ComposablePointcut union(Pointcut other) | 復(fù)合切點(diǎn)和一個(gè)切點(diǎn)對(duì)象進(jìn)行并集運(yùn)算。 |
如果需要對(duì)兩個(gè)切點(diǎn)做交集或并集運(yùn)算,那么可以使用org.springframework.aop.support.Pointcuts 工具類,它包含以下兩個(gè)靜態(tài)方法:
| 方法 | 說明 |
|---|---|
| Pointcut union(Pointcut pc1, Pointcut pc2) | 對(duì)兩個(gè)切點(diǎn)進(jìn)行交集運(yùn)算。 |
| Pointcut intersection(Pointcut pc1, Pointcut pc2) | 對(duì)兩個(gè)切點(diǎn)進(jìn)行并集運(yùn)算。 |
定義復(fù)合切點(diǎn):
public class RentComposablePointcut {
public Pointcut getIntersectionPointcut() {
ComposablePointcut pointcut = new ComposablePointcut();
//流程切點(diǎn)
ControlFlowPointcut pointcut1 = new ControlFlowPointcut(UserDelegate.class, "service");
//方法名切點(diǎn)
NameMatchMethodPointcut pointcut2 = new NameMatchMethodPointcut();
pointcut2.addMethodName("rent");
//交集操作
return pointcut.intersection((Pointcut) pointcut1).intersection((Pointcut) pointcut2);
}
}
配置:
<!-- 復(fù)合切點(diǎn)-->
<bean id="composablePointcut" class="net.deniro.spring4.aop.RentComposablePointcut"/>
<!-- 復(fù)合切面-->
<bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:pointcut="#{composablePointcut.intersectionPointcut}"
p:advice-ref="rentBeforeAdvice"/>
<!-- 代理類-->
<bean id="userProxy5"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="composableAdvisor"
p:target-ref="user"
p:proxyTargetClass="true"/>
這里在復(fù)合切面中,通過 p:pointcut 的 #{} 引用了 composablePointcut#getIntersectionPointcut() 方法來獲得復(fù)合切點(diǎn)。
單元測試:
User user = (User) context.getBean("userProxy5");
System.out.println("增強(qiáng)前-------------");
String userId = "001";
user.rent(userId);
user.back(userId);
System.out.println("增強(qiáng)后-------------");
UserDelegate delegate = new UserDelegate();
delegate.setUser(user);
delegate.service(userId);
輸出結(jié)果:
增強(qiáng)前-------------
User:租賃【充電寶】
User:歸還【充電寶】
增強(qiáng)后-------------
準(zhǔn)備租賃的用戶 ID:001
User:租賃【充電寶】
User:歸還【充電寶】
8 引介切面
引介切面是引介增強(qiáng)的封裝器,通過引介切面可以很容易的為現(xiàn)有對(duì)象添加任何接口的實(shí)現(xiàn)。

IntroductionAdvisor 僅有一個(gè)類過濾器 ClassFilter,因?yàn)橐榍忻媸穷惣?jí)別的。
IntroductionAdvisor 接口的兩個(gè)實(shí)現(xiàn)類:
| 類 | 描述 |
|---|---|
| DefaultIntroductionAdvisor | 默認(rèn)實(shí)現(xiàn)類。 |
| DeclareParentsAdvisor | 實(shí)現(xiàn)使用 AspectJ 語言的 DeclareParent 注解表示的引介切面。 |
DefaultIntroductionAdvisor 擁有三個(gè)構(gòu)造函數(shù):
| 構(gòu)造函數(shù) | 說明 |
|---|---|
| DefaultIntroductionAdvisor(Advice advice) | 通過一個(gè)增強(qiáng)創(chuàng)建的引介切面,它將為目標(biāo)對(duì)象中的所有接口的實(shí)現(xiàn)方法注入增強(qiáng)。 |
| DefaultIntroductionAdvisor(Advice advice, IntroductionInfo introductionInfo) | 通過一個(gè)增強(qiáng)和一個(gè) IntroductionInfo 創(chuàng)建引介切面,目標(biāo)對(duì)象需要實(shí)現(xiàn)的方法由 IntroductionInfo 對(duì)象的 getInterfaces() 方法返回。 |
| DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class<?> intf) | 通過增強(qiáng)與指定的接口類來創(chuàng)建引介切面,這個(gè)切面將對(duì)目標(biāo)對(duì)象的所有接口實(shí)現(xiàn)進(jìn)行增強(qiáng)。 |
配置:
<!-- 引介切面-->
<bean id="introductionAdvisor"
class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg>
<bean class="net.deniro.spring4.aop.RentBeforeAdvice"/>
</constructor-arg>
</bean>
<!-- 代理類-->
<bean id="userProxy6"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="introductionAdvisor"
p:target-ref="user"
p:proxyTargetClass="true"/>
單元測試:
User user = (User) context.getBean("userProxy6");
String userId = "001";
user.rent(userId);
user.back(userId);
輸出結(jié)果:
準(zhǔn)備租賃的用戶 ID:001
User:租賃【充電寶】
準(zhǔn)備租賃的用戶 ID:001
User:歸還【充電寶】
從輸出結(jié)果中可以看出,引介切面對(duì)所有的方法都實(shí)施了增強(qiáng)【因?yàn)槭褂昧?br>
DefaultIntroductionAdvisor(Advice advice) 構(gòu)造函數(shù)】。