Spring 中的 Pointcut API

本節說明 Spring 如何處理關鍵的 pointcut 概念。

概念

Spring 的 pointcut 模型允許獨立於 Advice 類型重複使用 pointcut。您可以使用相同的 pointcut 定義不同的 Advice。

org.springframework.aop.Pointcut 介面是核心介面,用於將 Advice 定義到特定的類別和方法。完整的介面如下:

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();
}

Pointcut 介面拆分為兩個部分,可以重複使用類別和方法比對部分,以及細緻的組合操作(例如,對另一個方法比對器執行「聯集」)。

ClassFilter 介面用於將 pointcut 限制為給定的一組目標類別。如果 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) 方法用於測試此 pointcut 是否曾經比對目標類別上的給定方法。此評估可以在建立 AOP 代理時執行,以避免在每次方法調用時進行測試。如果雙引數 matches 方法針對給定方法傳回 true,且 MethodMatcher 的 isRuntime() 方法傳回 true,則會在每次方法調用時調用三引數 matches 方法。這讓 pointcut 可以在目標 Advice 開始之前立即查看傳遞至方法調用的引數。

大多數 MethodMatcher 實作都是靜態的,這表示它們的 isRuntime() 方法傳回 false。在這種情況下,永遠不會調用三引數 matches 方法。

如果可以,請嘗試將 pointcut 設為靜態,以便 AOP 框架在建立 AOP 代理時快取 pointcut 評估的結果。

Pointcut 的操作

Spring 支援 pointcut 的操作(特別是聯集和交集)。

聯集表示任一 pointcut 比對的方法。交集表示兩個 pointcut 都比對的方法。聯集通常更有用。您可以使用 org.springframework.aop.support.Pointcuts 類別中的靜態方法,或使用相同套件中的 ComposablePointcut 類別來組合 pointcut。但是,使用 AspectJ pointcut 運算式通常是更簡單的方法。

AspectJ 運算式 Pointcut

自 2.0 版以來,Spring 使用的最重要的 pointcut 類型是 org.springframework.aop.aspectj.AspectJExpressionPointcut。這是一種 pointcut,它使用 AspectJ 提供的函式庫來剖析 AspectJ pointcut 運算式字串。

請參閱前一章以了解支援的 AspectJ pointcut 原語的討論。

便利的 Pointcut 實作

Spring 提供了幾種便利的 pointcut 實作。您可以直接使用其中一些;其他則旨在在應用程式特定的 pointcut 中進行子類別化。

靜態 Pointcut

靜態 pointcut 基於方法和目標類別,並且無法考量方法的引數。靜態 pointcut 足以應付大多數使用情況,而且是最佳的選擇。Spring 可以在方法首次調用時僅評估靜態 pointcut 一次。之後,無需在每次方法調用時再次評估 pointcut。

本節的其餘部分將說明 Spring 包含的一些靜態 pointcut 實作。

正則運算式 Pointcut

指定靜態 pointcut 的一種顯而易見的方法是正則運算式。除了 Spring 之外,還有幾個 AOP 框架也讓這成為可能。org.springframework.aop.support.JdkRegexpMethodPointcut 是一種通用的正則運算式 pointcut,它使用 JDK 中的正則運算式支援。

使用 JdkRegexpMethodPointcut 類別,您可以提供模式字串列表。如果其中任何一個符合,則 pointcut 評估為 true。(因此,結果 pointcut 實際上是指定模式的聯集。)

以下範例顯示如何使用 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 可以是攔截器、前置 Advice、拋出 Advice 等等)。在幕後,Spring 使用 JdkRegexpMethodPointcut。使用 RegexpMethodPointcutAdvisor 簡化了組態,因為一個 Bean 封裝了 pointcut 和 Advice,如下例所示:

  • 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 類型搭配使用。

屬性驅動的 Pointcut

一種重要的靜態 pointcut 類型是中繼資料驅動的 pointcut。這會使用中繼資料屬性的值(通常是來源層級中繼資料)。

動態 pointcut

與靜態 pointcut 相比,動態 pointcut 的評估成本更高。它們會考量方法引數以及靜態資訊。這表示它們必須在每次方法調用時進行評估,並且結果無法快取,因為引數會有所不同。

主要範例是 control flow pointcut。

控制流程 Pointcut

Spring 控制流程 pointcut 在概念上與 AspectJ cflow pointcut 相似,但功能較弱。(目前沒有辦法指定 pointcut 在另一個 pointcut 比對的連接點下方執行。)控制流程 pointcut 比對目前的呼叫堆疊。例如,如果連接點是由 com.mycompany.web 套件中的方法或 SomeCaller 類別調用,則可能會觸發。控制流程 pointcut 是使用 org.springframework.aop.support.ControlFlowPointcut 類別指定的。

與其他動態 pointcut 相比,控制流程 pointcut 在執行階段的評估成本顯著更高。在 Java 1.4 中,成本約為其他動態 pointcut 的五倍。

Pointcut 超類別

Spring 提供了有用的 pointcut 超類別,以協助您實作自己的 pointcut。

由於靜態 pointcut 最有用,因此您可能應該子類別化 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
	}
}

動態 pointcut 也有超類別。您可以將自訂 pointcut 與任何 Advice 類型搭配使用。

自訂 Pointcut

由於 Spring AOP 中的 pointcut 是 Java 類別,而不是語言功能(如 AspectJ 中),因此您可以宣告自訂 pointcut,無論是靜態還是動態。Spring 中的自訂 pointcut 可能會非常複雜。但是,如果可以,我們建議使用 AspectJ pointcut 運算式語言。

Spring 的後續版本可能會提供對 JAC 提供的「語意 pointcut」的支援,例如「目標物件中所有變更實例變數的方法」。