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,並且 MethodMatcher 的 isRuntime() 方法返回 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>

你可以搭配任何 Advice 型別使用 RegexpMethodPointcutAdvisor

屬性驅動的切入點

一種重要的靜態切入點是元資料驅動的切入點。它使用元資料屬性的值(通常是原始碼級別的元資料)。

動態切入點

動態切入點比靜態切入點評估成本更高。它們不僅考慮靜態資訊,還考慮方法引數。這意味著必須在每次方法呼叫時評估它們,並且結果無法快取,因為引數會變化。

主要示例是控制流切入點。

控制流切入點

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 提供的“語義切入點”支援——例如,“目標物件中所有改變例項變數的方法”。