宣告切入點
切入點決定了感興趣的連接點,因此使我們能夠控制建議何時運行。 Spring AOP 僅支援 Spring Bean 的方法執行連接點,因此您可以將切入點視為匹配 Spring Bean 上方法的執行。切入點宣告有兩個部分:簽名(包含名稱和任何參數)和切入點運算式(精確地確定我們感興趣的方法執行)。在 @AspectJ 註解樣式的 AOP 中,切入點簽名由常規方法定義提供,而切入點運算式通過使用 @Pointcut
註解來指示(用作切入點簽名的方法必須具有 void
返回類型)。
一個範例可能有助於釐清切入點簽名和切入點運算式之間的區別。以下範例定義了一個名為 anyOldTransfer
的切入點,該切入點匹配任何名為 transfer
的方法的執行
-
Java
-
Kotlin
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
構成 @Pointcut
註解值的切入點運算式是常規 AspectJ 切入點運算式。有關 AspectJ 切入點語言的完整討論,請參閱 AspectJ 程式設計指南(以及擴充功能,AspectJ 5 開發人員筆記本)或 AspectJ 的書籍之一(例如 Colyer 等人的 Eclipse AspectJ,或 Ramnivas Laddad 的 AspectJ in Action)。
支援的切入點指示符
Spring AOP 支援以下 AspectJ 切入點指示符 (PCD),用於切入點運算式
-
execution
:用於匹配方法執行連接點。這是使用 Spring AOP 時要使用的主要切入點指示符。 -
within
:將匹配限制在某些類型內的連接點(在使用 Spring AOP 時,在匹配類型中宣告的方法的執行)。 -
this
:將匹配限制在連接點(在使用 Spring AOP 時方法的執行),其中 Bean 參考(Spring AOP 代理)是給定類型的實例。 -
target
:將匹配限制在連接點(在使用 Spring AOP 時方法的執行),其中目標物件(正在代理的應用程式物件)是給定類型的實例。 -
args
:將匹配限制在連接點(在使用 Spring AOP 時方法的執行),其中引數是給定類型的實例。 -
@target
:將匹配限制在連接點(在使用 Spring AOP 時方法的執行),其中執行物件的類別具有給定類型的註解。 -
@args
:將匹配限制在連接點(在使用 Spring AOP 時方法的執行),其中傳遞的實際引數的運行時類型具有給定類型的註解。 -
@within
:將匹配限制在具有給定註解的類型內的連接點(在使用 Spring AOP 時,在具有給定註解的類型中宣告的方法的執行)。 -
@annotation
:將匹配限制在連接點,其中連接點的主題(在 Spring AOP 中運行的方法)具有給定註解。
由於 Spring AOP 將匹配限制為僅方法執行連接點,因此前面關於切入點指示符的討論給出了比您在 AspectJ 程式設計指南中找到的更狹窄的定義。此外,AspectJ 本身具有基於類型的語意,並且在執行連接點,this
和 target
都引用同一個物件:執行該方法的物件。 Spring AOP 是一個基於代理的系統,它區分代理物件本身(綁定到 this
)和代理後面的目標物件(綁定到 target
)。
由於 Spring AOP 框架的基於代理的性質,目標物件內的呼叫在定義上不會被攔截。對於 JDK 代理,只有代理上的公共介面方法呼叫可以被攔截。使用 CGLIB,代理上的公共和受保護方法呼叫會被攔截(必要時甚至包括套件可見的方法)。但是,通過代理的常見交互應始終通過公共簽名來設計。 請注意,切入點定義通常與任何被攔截的方法匹配。如果切入點嚴格來說僅適用於公共方法,即使在 CGLIB 代理場景中可能通過代理進行非公共交互,也需要相應地定義它。 如果您的攔截需求包括目標類別中的方法呼叫甚至建構子,請考慮使用 Spring 驅動的 原生 AspectJ 編織,而不是 Spring 的基於代理的 AOP 框架。這構成了一種具有不同特徵的不同 AOP 使用模式,因此在做出決定之前,請務必熟悉編織。 |
Spring AOP 還支援一個名為 bean
的附加 PCD。此 PCD 允許您將連接點的匹配限制為特定的命名 Spring Bean 或一組命名的 Spring Bean(當使用萬用字元時)。 bean
PCD 具有以下形式
bean(idOrNameOfBean)
idOrNameOfBean
Token 可以是任何 Spring Bean 的名稱。提供了使用 *
字元的有限萬用字元支援,因此,如果您為您的 Spring Bean 建立了一些命名約定,您可以編寫一個 bean
PCD 運算式來選擇它們。與其他切入點指示符一樣,bean
PCD 也可以與 &&
(and)、||
(or) 和 !
(negation) 運算子一起使用。
|
組合切入點運算式
您可以使用 &&,
||
和 !
來組合切入點運算式。您也可以按名稱引用切入點運算式。以下範例顯示了三個切入點運算式
-
Java
-
Kotlin
package com.xyz;
public class Pointcuts {
@Pointcut("execution(public * *(..))")
public void publicMethod() {} (1)
@Pointcut("within(com.xyz.trading..*)")
public void inTrading() {} (2)
@Pointcut("publicMethod() && inTrading()")
public void tradingOperation() {} (3)
}
1 | publicMethod 在方法執行連接點表示任何公共方法的執行時匹配。 |
2 | inTrading 在方法執行在交易模組中時匹配。 |
3 | tradingOperation 在方法執行表示交易模組中的任何公共方法時匹配。 |
package com.xyz
class Pointcuts {
@Pointcut("execution(public * *(..))")
fun publicMethod() {} (1)
@Pointcut("within(com.xyz.trading..*)")
fun inTrading() {} (2)
@Pointcut("publicMethod() && inTrading()")
fun tradingOperation() {} (3)
}
1 | publicMethod 在方法執行連接點表示任何公共方法的執行時匹配。 |
2 | inTrading 在方法執行在交易模組中時匹配。 |
3 | tradingOperation 在方法執行表示交易模組中的任何公共方法時匹配。 |
最佳實務是從較小的命名切入點建構更複雜的切入點運算式,如上所示。當按名稱引用切入點時,適用常規 Java 可見性規則(您可以在同一類型中看到 private
切入點,在階層中看到 protected
切入點,在任何地方看到 public
切入點,依此類推)。可見性不影響切入點匹配。
共享命名切入點定義
在使用企業應用程式時,開發人員通常需要從多個切面引用應用程式的模組和特定操作集。我們建議定義一個專用類別,用於捕獲此目的的常用命名切入點運算式。這樣的類別通常類似於以下 CommonPointcuts
範例(儘管您為類別命名的名稱由您決定)
-
Java
-
Kotlin
package com.xyz;
import org.aspectj.lang.annotation.Pointcut;
public class CommonPointcuts {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.abc.service and com.xyz.def.service) then
* the pointcut expression "execution(* com.xyz..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* DAO interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.dao.*.*(..))")
public void dataAccessOperation() {}
}
package com.xyz
import org.aspectj.lang.annotation.Pointcut
class CommonPointcuts {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.web..*)")
fun inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.service..*)")
fun inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.dao..*)")
fun inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.abc.service and com.xyz.def.service) then
* the pointcut expression "execution(* com.xyz..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz..service.*.*(..))")
fun businessService() {}
/**
* A data access operation is the execution of any method defined on a
* DAO interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.dao.*.*(..))")
fun dataAccessOperation() {}
}
您可以通過引用類別的完全限定名稱並結合 @Pointcut
方法的名稱,在任何需要切入點運算式的地方引用在此類別中定義的切入點。例如,要使服務層具有交易性,您可以編寫以下內容,該內容引用了 com.xyz.CommonPointcuts.businessService()
命名切入點
<aop:config>
<aop:advisor
pointcut="com.xyz.CommonPointcuts.businessService()"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
和 <aop:advisor>
元素在 基於 Schema 的 AOP 支援 中討論。交易元素在 交易管理 中討論。
範例
Spring AOP 使用者很可能會最常使用 execution
切入點指示符。 execution 運算式的格式如下
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除了返回類型模式(前面程式碼片段中的 ret-type-pattern
)、名稱模式和參數模式之外,所有部分都是可選的。返回類型模式確定方法為了匹配連接點而必須具有的返回類型。 *
最常用作返回類型模式。它匹配任何返回類型。完全限定類型名稱僅在方法返回給定類型時才匹配。名稱模式匹配方法名稱。您可以將 *
萬用字元用作名稱模式的全部或部分。如果您指定宣告類型模式,請包含尾隨 .
以將其連接到名稱模式組件。參數模式稍微複雜一些:()
匹配不帶參數的方法,而 (..)
匹配任意數量(零個或多個)的參數。 (*)
模式匹配帶有一個任何類型參數的方法。 (*,String)
匹配帶有兩個參數的方法。第一個可以是任何類型,而第二個必須是 String
。有關更多資訊,請參閱 AspectJ 程式設計指南的 語言語意 部分。
以下範例顯示了一些常見的切入點運算式
-
任何公共方法的執行
execution(public * *(..))
-
任何名稱以
set
開頭的方法的執行execution(* set*(..))
-
由
AccountService
介面定義的任何方法的執行execution(* com.xyz.service.AccountService.*(..))
-
在
service
套件中定義的任何方法的執行execution(* com.xyz.service.*.*(..))
-
在 service 套件或其子套件之一中定義的任何方法的執行
execution(* com.xyz.service..*.*(..))
-
service 套件中的任何連接點(僅在 Spring AOP 中為方法執行)
within(com.xyz.service.*)
-
service 套件或其子套件之一中的任何連接點(僅在 Spring AOP 中為方法執行)
within(com.xyz.service..*)
-
代理實作
AccountService
介面的任何連接點(僅在 Spring AOP 中為方法執行)this(com.xyz.service.AccountService)
this
更常用於綁定形式。有關如何在建議主體中使用代理物件,請參閱關於 宣告建議 的章節。 -
目標物件實作
AccountService
介面的任何連接點(僅在 Spring AOP 中為方法執行)target(com.xyz.service.AccountService)
target
更常用於綁定形式。有關如何在建議主體中使用目標物件,請參閱關於 宣告建議 的章節。 -
帶有單個參數且在運行時傳遞的引數為
Serializable
的任何連接點(僅在 Spring AOP 中為方法執行)args(java.io.Serializable)
args
更常用於綁定形式。有關如何在建議主體中使用方法引數,請參閱關於 宣告建議 的章節。請注意,此範例中給出的切入點與
execution(* *(java.io.Serializable))
不同。 args 版本在運行時傳遞的引數為Serializable
時匹配,而 execution 版本在方法簽名宣告單個Serializable
類型參數時匹配。 -
目標物件具有
@Transactional
註解的任何連接點(僅在 Spring AOP 中為方法執行)@target(org.springframework.transaction.annotation.Transactional)
您也可以在綁定形式中使用 @target
。有關如何在建議主體中使用註解物件,請參閱關於 宣告建議 的章節。 -
目標物件的宣告類型具有
@Transactional
註解的任何連接點(僅在 Spring AOP 中為方法執行)@within(org.springframework.transaction.annotation.Transactional)
您也可以在綁定形式中使用 @within
。請參閱宣告 Advice 章節,瞭解如何在 Advice 主體中使用註解物件。 -
任何連接點(在 Spring AOP 中僅限方法執行),其中執行的方法具有
@Transactional
註解@annotation(org.springframework.transaction.annotation.Transactional)
您也可以在綁定形式中使用 @annotation
。請參閱宣告 Advice 章節,瞭解如何在 Advice 主體中使用註解物件。 -
任何連接點(在 Spring AOP 中僅限方法執行),它接受單一參數,且傳遞的引數的執行時期型別具有
@Classified
註解@args(com.xyz.security.Classified)
您也可以在綁定形式中使用 @args
。請參閱宣告 Advice 章節,瞭解如何在 Advice 主體中使用註解物件。 -
任何在名為
tradeService
的 Spring Bean 上的連接點(在 Spring AOP 中僅限方法執行)bean(tradeService)
-
任何在名稱符合萬用字元運算式
*Service
的 Spring Bean 上的連接點(在 Spring AOP 中僅限方法執行)bean(*Service)
撰寫良好的 Pointcut
在編譯期間,AspectJ 會依序處理 Pointcut,以最佳化比對效能。檢查程式碼並判斷每個連接點是否符合給定的 Pointcut(靜態或動態)是一個成本很高的過程。(動態比對表示比對無法完全從靜態分析判斷,並且在程式碼中放置一個測試,以判斷程式碼執行時是否存在實際比對)。在第一次遇到 Pointcut 宣告時,AspectJ 會將其重寫為最佳化形式以進行比對過程。這代表什麼?基本上,Pointcut 會以 DNF(析取範式)重寫,並且 Pointcut 的組件會被排序,以便首先檢查那些評估成本較低的組件。這表示您不必擔心理解各種 Pointcut 設計器的效能,並且可以在 Pointcut 宣告中以任何順序提供它們。
然而,AspectJ 只能使用它被告知的內容。為了獲得最佳的比對效能,您應該考慮您嘗試達成的目標,並在定義中盡可能縮小比對的搜尋空間。現有的設計器自然分為三組:種類、範圍和上下文
-
種類設計器選擇特定種類的連接點:
execution
、get
、set
、call
和handler
。 -
範圍設計器選擇一組感興趣的連接點(可能有很多種類):
within
和withincode
-
上下文設計器根據上下文比對(並可選擇性地綁定):
this
、target
和@annotation
一個寫得好的 Pointcut 應至少包含前兩種型別(種類和範圍)。您可以包含上下文設計器,以根據連接點上下文進行比對,或綁定該上下文以在 Advice 中使用。僅提供種類設計器或僅提供上下文設計器可以運作,但可能會影響編織效能(使用的時間和記憶體),因為需要額外的處理和分析。範圍設計器比對速度非常快,使用它們表示 AspectJ 可以非常快速地排除不應進一步處理的連接點組。如果可能,一個好的 Pointcut 應始終包含一個範圍設計器。