7.2. Spring中的切入点API

让我们看看Spring是如何处理切入点这个重要概念的。

7.2.1. 概念

Spring的切入点模型使得切入点可以独立于通知类型进行重用,这就使得针对不同 advice使用相同的pointcut成为可能。

org.springframework.aop.Pointcut 是最核心的接口,用来将 通知应用于特定的类和方法,完整的接口定义如下:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut接口分割成有利于重用类和方法匹配的两部分,以及进行更细粒度的操作组合(例如与另一个方法匹配实现进行“或操作”)。

ClassFilter接口用来将切入点限定在一个给定的类集合中。如果matches()方法总是返回true,所有目标类都将被匹配:

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代理被创建的时候执行,这样可以避免在每次方法调用的时候都执行。如果matches(Method, Class )对于一个给定的方法返回true,并且isRuntime() 也返回true,那么matches(Method, Class , Object[])将在每个方法调用的时候被调用。这使得切入点在通知将被执行前可以查看传入到方法的参数。

大多数MethodMatcher是静态的,这意味着isRuntime()方法返回 false。在这种情况下,matches(Method, Class , Object[])永远不会被调用。

提示

应尽可能地使切入点是静态的,这就允许AOP框架在AOP代理被创建时缓存对切入点的计算结果。

7.2.2. 切入点实施

Spring在切入点上支持以下运算:

  • 或运算表示只需有一个切入点被匹配就执行方法。

  • 与运算表示所有的切入点都匹配的情况下才执行。

  • 通常或运算要更有用一些。

  • 切入点可以使用org.springframework.aop.support.Pointcuts类中的静态方法来编写,或者使用同一个包内的ComposablePointcut类。不过使用AspectJ切入点表达式通常会更简单一些。

7.2.3. AspectJ切入点表达式

从2.0开始,Spring中使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。 这个切入点使用AspectJ提供的库来解析满足AspectJ语法切入点表达式字符串。

可以查看前一章关于所支持的AspectJ切入点原语的讨论。

7.2.4. 便利的切入点实现

Spring提供了一些很方便的切入点实现。一些是开箱即用的,其它的是切入点应用规范的子集。

7.2.4.1. 静态切入点

静态切入点基于方法和目标类进行切入点判断而不考虑方法的参数。在多数情况下,静态切入点是高效的、最好的选择。 Spring只在第一次调用方法时执行静态切入点:以后每次调用这个方法时就不需要再执行。

让我们考虑Spring中的一些静态切入点实现。

7.2.4.1.1. 正则表达式切入点

显而易见,一种描述静态切入点的方式是使用正则表达式。包含Spring在内的一些AOP框架都支持这种方式。 org.springframework.aop.support.Perl5RegexpMethodPointcut是一个最基本的正则表达式切入点, 它使用Perl 5正则表达式语法。Perl5RegexpMethodPointcut依赖Jakarta ORO进行正则表达式匹配。 Spring也提供了JdkRegexpMethodPointcut类,它使用JDK 1.4或更高版本里提供的正则表达式支持。

使用Perl5RegexpMethodPointcut类,你可以提供一组模式字符串。 如果其中任意一个模式匹配,切入点将被解析为true。(实际上就是这些切入点的或集。)

下面显示用法:

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

Spring提供了一个方便的类 RegexpMethodPointcutAdvisor, 它也允许我们引用一个通知(记住这里一个通知可以是拦截器,前置通知(before advice),异常通知(throws advice)等类型中的一个)。 在背后,如果使用J2SE 1.4或者以上版本,Spring将使用JdkRegexpMethodPointcut,在之前版本的虚拟机上,Spring将退回去使用Perl5RegexpMethodPointcut。 可以通过设置perl5属性为true来强制使用Perl5RegexpMethodPointcut。使用RegexpMethodPointcutAdvisor可以简化织入,因为一个bean可以同时作为切入点和advisor,如下所示:

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

RegexpMethodPointcutAdvisor可以和任何通知类型一起使用

7.2.4.1.2. 属性驱动的切入点

一个重要的静态切入点是元数据驱动(metadata-driven)切入点。这使用元数据参数:特别是源代码级别的元数据。

7.2.4.2. 动态切入点

动态切入点比起静态切入点在执行时要消耗更多的资源。它们同时计算方法参数和静态信息。 这意味着它们必须在每次方法调用时都被执行;由于参数的不同,评估结果不能被缓存。

动态切入点的常见例子是控制流切入点。

7.2.4.2.1. 控制流切入点

Spring控制流切入点在概念上和AspectJ的cflow 切入点很相似, 虽然它的功能不如后者那么强大。(目前还不能让一个切入点在另外一个切入点所评估的连接点处执行)。 一个控制流切入点匹配当前的调用栈。例如,一个连接点被com.mycompany.web包内的一个 方法或者SomeCaller类调用,切入点就可能被激活。 控制流切入点是由org.springframework.aop.support.ControlFlowPointcut 类声明的。

注意

在执行时控制流切入点的开销是非常昂贵的,甚至与其它动态切入点比起来也是如此。在Java 1.4里,它的开销差不多是其它动态切入点的5倍;在Java 1.3中,这个比例甚至达到10倍。

7.2.5. 切入点的基类

Spring提供了很多有用的切入点基础类来帮助你实现你自己的切入点。

因为静态切入点是最常用的,你可能会像下面那样继承StaticMethodMatcherPointcut。这只需要实现一个抽象方法 (虽然也可以覆盖其它方法来定制行为):

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}

动态切入点也有很多基类。

你可以用Spring 1.0 RC2和更高版本里的自定义切入点及通知类型。

7.2.6. 自定义切入点

因为在Spring AOP中的切入点是Java类而不是语言的特性(后者像AspectJ中那样),所以可以声明自定义的切入点,不论是静态还是动态的。自定义切入点在Spring里可能很强大。即使这样我们仍推荐尽可能使用AspectJ切入点表达式语言。

注意

后续版本的Spring也许会提供“语义切入点”,像JAC所提供的那样:例如,“所有方法可以修改目标对象中实例变量”