这一节描述了 Spring 如何处理关键的 pointcut(切入点) 概念

概念

Spring 的切入点模型支持独立于 advice(通知)类型的切入点重用。你可以用相同的切入点针对不同的 advice。

org.springframework.aop.Pointcut接口是中心接口,用于将 advice 指向特定的类和方法。完整的接口如下:

  1. public interface Pointcut {
  2. ClassFilter getClassFilter();
  3. MethodMatcher getMethodMatcher();
  4. }

将 Pointcut 接口分成两部分,允许重用类和方法匹配部分以及细粒度的组合操作(比如与另一个方法匹配器进行 「联合」)。

ClassFilter 接口被用来将该 pointcut 限制在一组给定的目标类中。如果 matches()方法总是返回 true,所有的目标类都被匹配。下面的列表显示了 ClassFilter 接口的定义。

  1. public interface ClassFilter {
  2. boolean matches(Class clazz);
  3. }

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

  1. public interface MethodMatcher {
  2. boolean matches(Method m, Class<?> targetClass);
  3. boolean isRuntime();
  4. boolean matches(Method m, Class<?> targetClass, Object... args);
  5. }

matches(Method, Class)方法用来测试这个 pointcut 是否曾经匹配过目标类上的一个给定的方法。这个评估可以在创建 AOP 代理时进行,以避免在每个方法调用时进行测试。如果双参数匹配方法对一个给定的方法返回 true,并且 MethodMatcher 的 isRuntime()方法返回 true,那么三参数匹配方法将在每次方法调用时被调用。这让一个 pointcut 在目标 advice 开始之前立即查看传递给方法调用的参数。

大多数 MethodMatcher 实现是静态的(如果是动态的,2 个参数的 matches 方法会直接返回 true,那么就会去调用 3 个参数的 matches 的方法,因为动态的匹配需要关注参数),这意味着它们的 isRuntime()方法返回 false。在这种情况下,三个参数的匹配方法从未被调用。

:::tips 如果可能的话,尽量让 pointcuts 成为静态的,允许 AOP 框架在创建 AOP 代理时缓存 pointcuts 的评估结果。 :::

切入点操作

Spring 支持对 pointcuts 的操作(尤其是 union 和 intersection),这两个操作的对象都是针对两个 Pointcut 来说的:

  • union / 或则关系:指的是任何一个 pointcut 匹配的方法,则表示匹配。
  • intersection / 并且关系:指的是两个 pointcut 都匹配的方法,这表示匹配

看其中一个的方法定义就明白了
image.png

union 通常更有用。你可以通过使用 org.springframework.aop.support.Pointcuts类中的静态方法或通过使用同一包中的ComposablePointcut 类来组合切入点。然而,使用 AspectJ 的 pointcut 表达式通常是一种更简单的方法。

AspectJ 切入点表达式

从 2.0 开始,Spring 使用的最重要的 pointcut 类型是 org.springframework.aop.aspectj.AspectJExpressionPointcut。这是一个使用 AspectJ 提供的库来解析 AspectJ pointcut 表达式字符串的 pointcut。

有关受支持的 AspectJ 切入点的讨论,请参见 前一章

切入点实现

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

静态切入点

静态切入点是基于方法和目标类的,不能考虑到方法的参数。对于大多数的使用来说,静态的切入点已经足够了,而且是最好的。Spring 只能在一个方法第一次被调用时评估一次静态的切入点。在那之后,就不需要在每次调用方法时再次评估该节点了。

本节的其余部分描述了 Spring 中包含的一些静态切入点的实现。

正则表达式切入点

一个明显的指定静态点切的方法是正则表达式。org.springframework.aop.support.JdkRegexpMethodPointcut是一个通用的正则表达式点切,它使用 JDK 中的正则表达式支持。

通过 JdkRegexpMethodPointcut 类,你可以提供一个模式字符串的列表。如果其中任何一个是匹配的,那么这个切入点就会被评估为 true。(因此,产生的切入点实际上是指定模式的联合(其中任意一个匹配)。)

  1. <bean id="settersAndAbsquatulatePointcut"
  2. class="org.springframework.aop.support.JdkRegexpMethodPointcut">
  3. <property name="patterns">
  4. <list>
  5. <value>.*set.*</value>
  6. <value>.*absquatulate</value>
  7. </list>
  8. </property>
  9. </bean>

Spring 提供了一个名为 RegexpMethodPointcutAdvisor 的方便类,它让我们也可以引用一个 Advice(记住,一个 Advice 可以是一个 interceptor/拦截器、before advice、throws advice,以及其他)。在幕后,Spring 使用 JdkRegexpMethodPointcut。使用RegexpMethodPointcutAdvisor 可以简化布线,因为这个 bean 同时封装了 Pointcut 和 Advice,正如下面的例子所示:

  1. <bean id="settersAndAbsquatulateAdvisor"
  2. class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  3. <property name="advice">
  4. <ref bean="beanNameOfAopAllianceInterceptor"/>
  5. </property>
  6. <property name="patterns">
  7. <list>
  8. <value>.*set.*</value>
  9. <value>.*absquatulate</value>
  10. </list>
  11. </property>
  12. </bean>

您可以将 RegexpMethodPointcutAdvisor 与任何 Advice 类型一起使用。

编程例子如下:

  1. package cn.mrcode.study.springdocsread.aspect;
  2. import org.aopalliance.intercept.MethodInterceptor;
  3. import org.aopalliance.intercept.MethodInvocation;
  4. import org.springframework.aop.Pointcut;
  5. import org.springframework.aop.support.JdkRegexpMethodPointcut;
  6. import org.springframework.aop.support.RegexpMethodPointcutAdvisor;
  7. import java.lang.reflect.Method;
  8. /**
  9. * @author mrcode
  10. */
  11. public class DemoTest {
  12. public static void main(String[] args) throws NoSuchMethodException {
  13. final JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
  14. pointcut.setPatterns(".*set.*", ".*absquatulate");
  15. final RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
  16. advisor.setAdvice(new MethodInterceptor() {
  17. @Override
  18. public Object invoke(MethodInvocation invocation) throws Throwable {
  19. return invocation.proceed();
  20. }
  21. });
  22. advisor.setPatterns("cn.mrcode.study.springdocsread.aspect.Simple*.*(..)", ".*absquatulate");
  23. // 这里获取到的 pointcut 就是上面的 JdkRegexpMethodPointcut
  24. final Pointcut pointcut = advisor.getPointcut();
  25. // 获取到一个类中的方法
  26. final Method foo = SimplePojo.class.getMethod("foo");
  27. // 然后:匹配这个方法在目标类中 是否被这里指定设 正则表达式所匹配到
  28. // 尝试将正则表达式与目标类的完全限定名称以及方法的声明类以及方法的名称进行匹配
  29. // 返回的结果就是, 是否静态匹配
  30. // 第二个参数其实是可以为 null 的,如果为 null 这会从 Method 的 method.getDeclaringClass() 获取该方法所在类的 class,从而获取获取到完整的类限定名称
  31. final boolean matches = ((JdkRegexpMethodPointcut) pointcut).matches(foo, SimplePojo.class);
  32. System.out.println(matches);
  33. }
  34. }

属性驱动切入点

一个重要的静态切点类型是元数据驱动的切入点。它使用元数据属性的值(通常是源级元数据)。

动态切入点

动态切入点比静态切入点的评估成本更高。它们考虑到了方法的参数以及静态信息。这意味着它们必须在每次调用方法时进行评估,而且结果不能被缓存,因为参数会有变化。

主要的例子是控制流(control flow)的切入点

控制流(control flow)切入点

Spring 的控制流切入点在概念上与 AspectJ 的 cflow 切入点相似,但功能较弱。(目前还没有办法指定一个切入点在另一个切入点所匹配的连接点(匹配到的实际的运行方法)下面运行)。一个控制流切入点与当前的调用栈相匹配。例如,如果连接点被 com.mycompany.web包中的方法或 SomeCaller 类所调用,它就会启动。控制流切点是通过使用 org.springframework.aop.support.ControlFlowPointcut 类指定的。

:::info 控制流切入点在运行时的评估成本甚至比其他动态切入点高得多。在 Java 1.4 中,其成本大约是其他动态切点的五倍。 :::

Pointcut 的超类

Spring 提供了有用的 pointcut 超类来帮助你实现你自己的切入点

因为静态切入点是最有用的,所以你可能应该子类化 StaticMethodMatcherPointcut。这只需要实现一个抽象方法(尽管你可以覆盖其他方法来定制行为)。下面的例子展示了如何子类化 StaticMethodMatcherPointcut:

  1. class TestStaticPointcut extends StaticMethodMatcherPointcut {
  2. public boolean matches(Method m, Class targetClass) {
  3. // return true if custom criteria match
  4. }
  5. }

也有一些超类用于动态的 pointcuts。你可以在任何 advice 类型中使用自定义的切入点

自定义切入点

因为 Spring AOP 中的切入点是 Java 类,而不是语言特性(如 AspectJ),所以你可以声明自定义切入点,无论是静态还是动态的。在 Spring 中,自定义的 pointcuts 可以是任意复杂的。然而,如果可以的话,我们建议使用 AspectJ 的切入点表达语言。

:::info 更高版本的 Spring 版本可能会提供对 JAC 所提供的 「语义切入点」的支持 — 例如,「所有改变目标对象中实例变量的方法」。 :::