說說在 Spring 中如何創(chuàng)建切面(AOP)

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

Pointcut 類關(guān)系圖

Spring 通過 Pointcut 來描述切點(diǎn),它是由 ClassFilter 和 MethodMatcher 組成的。ClassFilter 用于定位到特定的類,MethodMatcher 用于定位到特定的方法上。

ClassFilter 自定義了一個(gè)方法 matches(Class<?> clazz),clazz 表示被檢查的類,這個(gè)方法用來判斷是否是條件所要求的類。

Spring 支持兩種方法匹配器:

  1. 靜態(tài)方法匹配器 - 僅對(duì)方法簽名(方法名、入?yún)㈩愋团c順序)進(jìn)行匹配;僅判別一次。
  2. 動(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è)主要的子類,它們是 NameMatchMethodPointcutAbstractRegexpMethodPointcut ,前者提供簡單字符串匹配方法簽名,而后者是使用正則表達(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 類圖

切面可分為以下三類:

切面類型 說明
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 類關(guān)系圖

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)。

引介切面類關(guā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ù)】。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容