跳到主要内容
版本:6.2.3

Spring 中的 Pointcut API

ChatGPT-4o-mini 中英对照 Pointcut API in Spring

本节描述了 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,并且 MethodMatcher 的 isRuntime() 方法返回 true,则在每次方法调用时都会调用三个参数的 matches 方法。这使得切入点可以在目标通知开始之前立即查看传递给方法调用的参数。

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

提示

如果可能的话,尽量将切点设置为静态,这样可以让 AOP 框架在创建 AOP 代理时缓存切点评估的结果。

对切入点的操作​

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

Union 意味着任一切入点匹配的方法。Intersection 意味着两个切入点都匹配的方法。Union 通常更有用。您可以通过使用 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:

@Configuration
public class JdkRegexpConfiguration {

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

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

@Configuration
public class RegexpConfiguration {

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

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

属性驱动的切入点​

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

动态切点​

动态切入点的评估成本高于静态切入点。它们不仅考虑方法参数,还考虑静态信息。这意味着它们必须在每次方法调用时进行评估,并且结果无法缓存,因为参数会有所不同。

主要示例是 control flow 切入点。

控制流切入点​

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

备注

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

切点超类​

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

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

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

动态切点还有超类。您可以使用自定义切点与任何建议类型。

自定义切点​

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

备注

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