6. Spring AOP APIs

在本章中,我们将讨论较底层的Spring AOP API。对于常见的应用程序,我们建议使用带有AspectJ切点的Spring AOP,如前一章所述。

6.1. Spring切点API

本节介绍Spring如何处理关键的切入点原理。

6.1.1. 原理

Spring的切入点模型支持独立于通知类型的切入点重用。您可以为不同的目标使用相同的切入点。
org.springframework.aop.Pointcut接口是一个中心接口,用于将通知定向到特定的类和方法。完整的接口如下:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

将 Pointcut i接口拆分为两部分以允许重用类和方法匹配部分以及细粒度组合操作(例如,使用另一个方法匹配器执行“union”)。
ClassFilter接口用于将切入点限制为一组给定的目标类。如果matches() 方法始终返回true,则所有目标类都匹配。下面的列表显示ClassFilter接口定义:

public interface ClassFilter {

    boolean matches(Class clazz);
}

MethodMatcher 接口通常更重要。完整的接口如下:

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}

matches(Method, Class) 方法用于测试此切入点是否与目标类上的给定方法匹配。当创建AOP代理时可以执行此评估,以避免对每个方法调用进行测试。如果两个参数匹配的方法对于给定的方法返回true,而MethodMatcher 的 isRuntime() 方法返回true,则在每次方法调用时都会调用三个参数匹配的方法。这允许切入点查看在执行目标通知之前立即传递给方法调用的参数。
大多数MethodMatcher 实现是静态的,这意味着它们的isRuntime() 方法返回false。在本例中,从不调用三个参数的matches方法。

如果可能,尝试使切入点静态化,允许AOP框架在创建AOP代理时缓存切入点评估的结果。

6.1.2. 切入点操作

Spring支持对切入点的操作(尤其是并集和交集)。
联合是指与切入点匹配的方法。交集表示两个切入点匹配的方法。联合通常更有用。您可以使用org.springframework.aop.support.Pointcuts 类中的静态方法或在同一个包中使用ComposablePointcut类来撰写切入点。然而,使用AspectJ切入点表达式通常是一种更简单的方法。

6.1.3.AspectJ表达式切入点

从2.0开始,Spring使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。这是一个切入点,它使用AspectJ提供的库来分析AspectJ切入点表达式字符串。
有关支持的aspectj切入点表达语的讨论,请参阅上一章。

6.1.4.几个便捷的切入点实现

Spring提供了几个方便的切入点实现。你可以直接使用其中的一些。其他的则打算在特定于应用程序的切入点中进行子类化。

静态切入点
静态切入点基于方法和目标类,不能考虑方法的参数。静态切入点对于大多数使用来说是最好的。当第一次调用方法时,Spring只能计算一次静态切入点。之后,不需要对每个方法调用再次计算切入点。
本节的其余部分描述了Spring中包含的一些静态切入点实现。
正则表达式切入点
指定静态切入点的一个明显方法是正则表达式。除了Spring,还有可能是其它几个AOP框架。org.springframework.aop.support.JdkRegexpMethodPointcut是一个通用的正则表达式切入点,它使用JDK中的正则表达式支持。
通过JdkRegexpMethodPointcut类,可以提供模式字符串的列表。如果其中任何一个匹配,则切入点的计算结果为true。(因此,结果实际上是这些切点的并集。)
下面的示例演示如何使用JdkRegexpMethodPointcut:

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring提供了一个名为RegexpMethodPointcutAdvisor的方便类(这个允许我们引用一个通知)。在幕后,Spring使用了JdkRegexpMethodPointcut。使用RegexpMethodPointcutAdvisor 简化了连接,因为一个bean同时封装了pointcut和advice,如下示例所示:

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

可以将RegexpMethodPointcutAdvisor与任何通知类型一起使用。
属性驱动的切入点
一种重要的静态切入点类型是元数据驱动的切入点。这将使用元数据属性的值(通常是源级元数据)。
动态切入点

动态切入点的计算成本比静态切入点高。它们考虑了方法参数和静态信息。这意味着必须对每个方法进行评估,并且不能缓存结果,因为参数会有所不同。

主要的例子是control flow切入点。

控制流切入点

Spring的control flow(控制流)切入点在概念上与AspectJ cflow切入点相似,但功能较弱。(目前无法指定在与另一个切入点匹配的连接点下方执行切入点)控制流切入点与当前调用堆栈匹配。例如,如果连接点是由com.mycompany.web包中的方法或SomeCaller类调用的,则它可能会激发。使用org.springframework.aop.support.ControlFlowPointcut类指定控制流切入点。

控制流切入点在运行时的计算成本比其他动态切入点都要高。在Java 1.4中,成本是其他动态切入点的五倍。

6.1.5 切入点超类

Spring提供了一个有用的切入点超类来帮助您实现自己的切入点。

因为静态切入点最有用,所以您可能应该将StaticMethodMatcherPointCut子类化。这只需要实现一个抽象方法(尽管您可以重写其他方法来定制行为)。下面的示例显示如何将StaticMethodMatcherPointCut子类化:

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // 如果自定义条件匹配,则返回true
    }
}

还有动态切入点的超类。您可以对任何通知类型使用自定义切入点。

6.1.6. 自定义切入点

因为Spring AOP中的切入点是Java类,而不是语言特征(如AspectJ中),所以可以声明自定义切入点,无论是静态的还是动态的。弹簧中的自定义切入点可以任意形式。但是,如果可以的话,我们建议使用AspectJ切入点表达式语言。

Spring的较新版本可能支持JAC提供的“语义切入点”,例如,“所有更改目标对象中实例变量的方法”。