Spring 中的切点 API

本节描述了 Spring 如何处理关键的切点概念。

概念

Spring 的切点模型允许切点重用独立于通知类型。您可以使用相同的切点来定位不同的通知。

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

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();
}

Pointcut 接口拆分为两部分允许重用类和方法匹配部分以及细粒度的组合操作(例如,与另一个方法匹配器执行“联合”)。

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

  • Java

  • Kotlin

  • Xml

@Configuration
public class JdkRegexpConfiguration {

	@Bean
	public JdkRegexpMethodPointcut settersAndAbsquatulatePointcut() {
		JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
		pointcut.setPatterns(".*set.*", ".*absquatulate");
		return pointcut;
	}
}
@Configuration
class JdkRegexpConfiguration {

	@Bean
	fun settersAndAbsquatulatePointcut() = JdkRegexpMethodPointcut().apply {
		setPatterns(".*set.*", ".*absquatulate")
	}
}
<bean id="settersAndAbsquatulatePointcut"
	  class="org.springframework.aop.support.JdkRegexpMethodPointcut">
	<property name="patterns">
		<list>
			<value>.*set.*</value>
			<value>.*absquatulate</value>
		</list>
	</property>
</bean>

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

  • Java

  • Kotlin

  • Xml

@Configuration
public class RegexpConfiguration {

	@Bean
	public RegexpMethodPointcutAdvisor settersAndAbsquatulateAdvisor(Advice beanNameOfAopAllianceInterceptor) {
		RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
		advisor.setAdvice(beanNameOfAopAllianceInterceptor);
		advisor.setPatterns(".*set.*", ".*absquatulate");
		return advisor;
	}
}
@Configuration
class RegexpConfiguration {

	@Bean
	fun settersAndAbsquatulateAdvisor(beanNameOfAopAllianceInterceptor: Advice) = RegexpMethodPointcutAdvisor().apply {
		advice = beanNameOfAopAllianceInterceptor
		setPatterns(".*set.*", ".*absquatulate")
	}
}
<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 与任何 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 提供的“语义切点”的支持——例如,“目标对象中更改实例变量的所有方法”。