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 的評估成本更高。它們會考量方法引數以及靜態資訊。這表示它們必須在每次方法調用時進行評估,並且結果無法快取,因為引數會有所不同。
主要範例是 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 類型搭配使用。