Pointcut API in Spring

本部分介绍了 Spring 如何处理关键切入点概念。

This section describes how Spring handles the crucial pointcut concept.

Concepts

Spring 的切入点模型支持与建议类型无关的切入点重用。你可以使用相同的切入点针对不同的建议。

Spring’s pointcut model enables pointcut reuse independent of advice types. You can target different advice with the same pointcut.

org.springframework.aop.Pointcut 接口是中心接口,用于为特定类和方法提供指导。完整的界面如下:

The org.springframework.aop.Pointcut interface is the central interface, used to target advice to particular classes and methods. The complete interface follows:

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();
}

Pointcut 接口分为两部分,可以实现类和方法匹配部分的重用,以及精细组合运算(例如与其他方法匹配器执行“并集”)。

Splitting the Pointcut interface into two parts allows reuse of class and method matching parts and fine-grained composition operations (such as performing a “union” with another method matcher).

ClassFilter 接口用于将切点限制为一个给定的目标类集合。如果 matches() 方法总是返回 true,则匹配所有目标类。以下清单显示了 ClassFilter 接口:

The ClassFilter interface is used to restrict the pointcut to a given set of target classes. If the matches() method always returns true, all target classes are matched. The following listing shows the ClassFilter interface definition:

public interface ClassFilter {

	boolean matches(Class clazz);
}

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

The MethodMatcher interface is normally more important. The complete interface follows:

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 方法将在每个方法调用时被调用。这使得切点能在目标建议开始之前的立即查看传递给方法调用的参数。

The matches(Method, Class) method is used to test whether this pointcut ever matches a given method on a target class. This evaluation can be performed when an AOP proxy is created to avoid the need for a test on every method invocation. If the two-argument matches method returns true for a given method, and the isRuntime() method for the MethodMatcher returns true, the three-argument matches method is invoked on every method invocation. This lets a pointcut look at the arguments passed to the method invocation immediately before the target advice starts.

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

Most MethodMatcher implementations are static, meaning that their isRuntime() method returns false. In this case, the three-argument matches method is never invoked.

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

If possible, try to make pointcuts static, allowing the AOP framework to cache the results of pointcut evaluation when an AOP proxy is created.

Operations on Pointcuts

Spring 支持切点上的运算(特别是并集和交集)。

Spring supports operations (notably, union and intersection) on pointcuts.

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

Union means the methods that either pointcut matches. Intersection means the methods that both pointcuts match. Union is usually more useful. You can compose pointcuts by using the static methods in the org.springframework.aop.support.Pointcuts class or by using the ComposablePointcut class in the same package. However, using AspectJ pointcut expressions is usually a simpler approach.

AspectJ Expression Pointcuts

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

Since 2.0, the most important type of pointcut used by Spring is org.springframework.aop.aspectj.AspectJExpressionPointcut. This is a pointcut that uses an AspectJ-supplied library to parse an AspectJ pointcut expression string.

请参阅 previous chapter 以了解受支持的 AspectJ 切入点基元讨论。

See the previous chapter for a discussion of supported AspectJ pointcut primitives.

Convenience Pointcut Implementations

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

Spring provides several convenient pointcut implementations. You can use some of them directly; others are intended to be subclassed in application-specific pointcuts.

Static Pointcuts

静态切点基于方法和目标类,且无法考虑方法的参数。对于大多数用法,静态切点就足够了——而且是最好的。当首先调用一个方法时,Spring 只能评估一次静态切点。在那之后,无需通过每次方法调用重新评估切点。

Static pointcuts are based on the method and the target class and cannot take into account the method’s arguments. Static pointcuts suffice — and are best — for most usages. Spring can evaluate a static pointcut only once, when a method is first invoked. After that, there is no need to evaluate the pointcut again with each method invocation.

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

The rest of this section describes some of the static pointcut implementations that are included with Spring.

Regular Expression Pointcuts

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

One obvious way to specify static pointcuts is regular expressions. Several AOP frameworks besides Spring make this possible. org.springframework.aop.support.JdkRegexpMethodPointcut is a generic regular expression pointcut that uses the regular expression support in the JDK.

使用 JdkRegexpMethodPointcut 类,你可以提供一个模式字符串列表。如果其中任何一个是匹配项,切点将评估为 true。(结果,所得到的切点实际上是指定模式的并集。)

With the JdkRegexpMethodPointcut class, you can provide a list of pattern strings. If any of these is a match, the pointcut evaluates to true. (As a consequence, the resulting pointcut is effectively the union of the specified patterns.)

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

The following example shows how to use 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 封装了切点和通知,如下例所示:

Spring provides a convenience class named RegexpMethodPointcutAdvisor, which lets us also reference an Advice (remember that an Advice can be an interceptor, before advice, throws advice, and others). Behind the scenes, Spring uses a JdkRegexpMethodPointcut. Using RegexpMethodPointcutAdvisor simplifies wiring, as the one bean encapsulates both pointcut and advice, as the following example shows:

  • 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 结合使用。

You can use RegexpMethodPointcutAdvisor with any Advice type.

Attribute-driven Pointcuts

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

An important type of static pointcut is a metadata-driven pointcut. This uses the values of metadata attributes (typically, source-level metadata).

Dynamic pointcuts

相较于静态切入点而言,动态切入点的评估成本更高。它们会同时考虑方法参数和静态信息。这意味着它们必须随每次方法调用一起评估,且结果无法缓存,因为参数会发生变化。

Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account method arguments as well as static information. This means that they must be evaluated with every method invocation and that the result cannot be cached, as arguments will vary.

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

The main example is the control flow pointcut.

Control Flow Pointcuts

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

Spring control flow pointcuts are conceptually similar to AspectJ cflow pointcuts, although less powerful. (There is currently no way to specify that a pointcut runs below a join point matched by another pointcut.) A control flow pointcut matches the current call stack. For example, it might fire if the join point was invoked by a method in the com.mycompany.web package or by the SomeCaller class. Control flow pointcuts are specified by using the org.springframework.aop.support.ControlFlowPointcut class.

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

Control flow pointcuts are significantly more expensive to evaluate at runtime than even other dynamic pointcuts. In Java 1.4, the cost is about five times that of other dynamic pointcuts.

Pointcut Superclasses

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

Spring provides useful pointcut superclasses to help you to implement your own pointcuts.

由于静态切入点最常用,因此你可能需要继承 StaticMethodMatcherPointcut。这只需要实现一个抽象方法(不过你也可以覆盖其他方法以自定义行为)。以下示例展示了如何继承 StaticMethodMatcherPointcut

Because static pointcuts are most useful, you should probably subclass StaticMethodMatcherPointcut. This requires implementing only one abstract method (although you can override other methods to customize behavior). The following example shows how to subclass 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
	}
}

还有一些动态切入点超类。你可以将自定义切入点与任何类型的建议结合使用。

There are also superclasses for dynamic pointcuts. You can use custom pointcuts with any advice type.

Custom Pointcuts

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

Because pointcuts in Spring AOP are Java classes rather than language features (as in AspectJ), you can declare custom pointcuts, whether static or dynamic. Custom pointcuts in Spring can be arbitrarily complex. However, we recommend using the AspectJ pointcut expression language, if you can.

Spring 的后续版本可能会提供 JAC 所提供的 “semantic pointcuts” 支持,例如 “all methods that change instance variables in the target object.”

Later versions of Spring may offer support for “semantic pointcuts” as offered by JAC — for example, “all methods that change instance variables in the target object.”