跳到主要内容
版本:7.0.3

Spring中的Pointcut API

Hunyuan 7b 中英对照 Pointcut API in Spring

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

概念

Spring的切点模型(pointcut model)使得切点的复用独立于通知类型(advice types)。你可以使用同一个切点来针对不同的通知。

org.springframework.aop.Pointcut接口是核心接口,用于将通知(advise)应用到特定的类和方法上。该接口的完整定义如下:

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方法永远不会被调用。

提示

如果可能的话,尽量使切点(pointcuts)保持静态,这样当创建AOP代理时,AOP框架就可以缓存切点评估的结果。

对切点的操作

Spring支持对切点(pointcuts)进行操作(尤其是并集和交集操作)。

“Union”指的是至少有一个切点匹配到的方法。“Intersection”指的是两个切点都匹配到的方法。通常情况下,“Union”更为实用。你可以通过使用org.springframework.aop.support.Pointcuts类中的静态方法,或者使用同一包中的ComposablePointcut类来组合切点。不过,使用AspectJ的切点表达式通常是一种更简单的做法。

AspectJ 表达式切点

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

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

便利性切点实现

Spring提供了几种方便的切点(pointcut)实现。你可以直接使用其中的一些;其他的则是为了在特定于应用的切点中被继承而设计的。

静态切点

静态切点是基于方法和目标类来定义的,因此无法考虑方法的参数。对于大多数使用场景来说,静态切点已经足够了,也是最佳选择。Spring 只能在方法首次被调用时评估一次静态切点;之后,在每次方法调用时都不需要再次评估该切点。

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

正则表达式切点

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

使用JdkRegexpMethodPointcut类,你可以提供一组模式字符串。如果其中有任何一个匹配,那么该切点(pointcut)的评估结果就为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一起使用。

属性驱动的切点

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

动态切点

动态切点(dynamic pointcuts)的评估成本比静态切点(static pointcuts)更高。它们不仅考虑静态信息,还考虑方法参数。这意味着每次方法调用时都必须进行评估,且评估结果无法被缓存,因为参数会发生变化。

主要例子是“控制流”切点(control flow pointcut)。

控制流切点

Spring的控制流切点(control flow pointcuts)在概念上与AspectJ的cflow切点类似,尽管功能稍弱。(目前还没有办法指定一个切点在另一个切点匹配的连接点(join point)之后执行。)控制流切点会匹配当前的调用栈(call stack)。例如,如果连接点是由com.mycompany.web包中的方法或SomeCaller类调用的,那么控制流切点就可能被触发。控制流切点是使用org.springframework.aop.support.ControlFlowPointcut类来指定的。

备注

在运行时评估控制流断点(control flow pointcuts)的成本,甚至比其他动态断点(dynamic pointcuts)的代价还要高。在Java 1.4中,其成本大约是其他动态断点的五倍。

Pointcut Superclasses

Spring提供了有用的切点超类(pointcut superclasses),以帮助你实现自己的切点(pointcuts)。

因为静态切点(static pointcuts)最为实用,你可能应该继承StaticMethodMatcherPointcut类。这样做只需要实现一个抽象方法(尽管你也可以覆盖其他方法来定制行为)。以下示例展示了如何继承StaticMethodMatcherPointcut

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

还有用于动态切点的超类。你可以将自定义切点与任何类型的建议(advice)一起使用。

自定义切点

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

备注

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