Spring 中的切入点 API

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

概念

Spring 的切入点模型支持独立于通知类型重用切入点。你可以使用相同的切入点来定位不同的通知。

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

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();
}

Pointcut 接口拆分为两部分,可以重用类和方法匹配部分,并进行细粒度的组合操作(例如,与另一个方法匹配器执行“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 代理时执行,以避免在每次方法调用时进行测试。如果两参数的 matches 方法对于给定方法返回 true,并且 MethodMatcherisRuntime() 方法返回 true,则在每次方法调用时都会调用三参数的 matches 方法。这允许切入点在目标通知开始之前立即查看传递给方法调用的参数。

大多数 MethodMatcher 实现是静态的,这意味着它们的 isRuntime() 方法返回 false。在这种情况下,三参数的 matches 方法永远不会被调用。

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

切入点上的操作

Spring 支持对切入点进行操作(特别是联合和交集)。

联合意味着任一切入点匹配的方法。 交集意味着两个切入点都匹配的方法。 联合通常更有用。 你可以通过使用 org.springframework.aop.support.Pointcuts 类中的静态方法或使用同一包中的 ComposablePointcut 类来组合切入点。然而,使用 AspectJ 切入点表达式通常是一种更简单的方法。

AspectJ 表达式切入点

自 2.0 版本以来,Spring 使用的最重要的切入点类型是 org.springframework.aop.aspectj.AspectJExpressionPointcut。这是一个使用 AspectJ 提供的库来解析 AspectJ 切入点表达式字符串的切入点。

有关支持的 AspectJ 切入点原语的讨论,请参阅 上一章

方便的切入点实现

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

静态切入点

静态切入点基于方法和目标类,不能考虑方法的参数。静态切入点足以满足大多数用途,并且是最好的选择。Spring 只能在方法首次调用时评估一次静态切入点。之后,无需在每次方法调用时再次评估切入点。

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

正则表达式切入点

指定静态切入点的一种显而易见的方法是正则表达式。除了 Spring 之外,还有几个 AOP 框架也提供了这种功能。org.springframework.aop.support.JdkRegexpMethodPointcut 是一个通用的正则表达式切入点,它使用 JDK 中的正则表达式支持。

使用 JdkRegexpMethodPointcut 类,你可以提供一个模式字符串列表。如果其中任何一个匹配,则切入点评估为 true。(因此,结果切入点实际上是指定模式的联合。)

以下示例展示了如何使用 JdkRegexpMethodPointcut

Spring 提供了一个名为 RegexpMethodPointcutAdvisor 的便利类,它允许我们同时引用一个 Advice(请记住,Advice 可以是拦截器、前置通知、抛出通知等)。在幕后,Spring 使用 JdkRegexpMethodPointcut。使用 RegexpMethodPointcutAdvisor 简化了连接,因为一个 bean 封装了切入点和通知,如以下示例所示:

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

属性驱动的切入点

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

动态切入点

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

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

控制流切入点

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

控制流切入点在运行时评估成本明显高于其他动态切入点。在 Java 1.4 中,其成本约为其他动态切入点的五倍。

切入点超类

Spring 提供了有用的切入点超类,以帮助你实现自己的切入点。

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

  • Java

  • Kotlin

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

	override fun matches(method: Method, targetClass: Class<*>): Boolean {
		// return true if custom criteria match
	}
}

也有用于动态切入点的超类。 你可以将自定义切入点与任何通知类型一起使用。

自定义切入点

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

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