您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“如何激活@AspectJ支持”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
面向切面的編程(AOP)通過提供另一種思考程序結(jié)構(gòu)的方式來補充面向?qū)ο竦木幊蹋∣OP)。OOP中模塊化的關(guān)鍵單元是<u>類</u>,而在AOP中模塊化是<u>切面</u>。切面使關(guān)注點(例如事務(wù)管理)的模塊化可以跨越多種類型和對象。(這種關(guān)注在AOP文獻(xiàn)中通常被稱為“跨領(lǐng)域”關(guān)注。)
Spring的關(guān)鍵組件之一是AOP框架。雖然Spring IoC容器不依賴于AOP(這意味著如果你不想使用AOP,就不需要使用AOP),但AOP對Spring IoC進(jìn)行了補充,提供了一個非常強大的中間件解決方案。
具有AspectJ切入點的Spring AOP
Spring提供了使用基于schema的方法或@AspectJ注解樣式來編寫自定義切面的簡單而強大的方法。這兩種樣式都提供了完全類型化的建議,并使用了AspectJ切入點語言,同時仍然使用Spring AOP進(jìn)行編織。
本章討論基于
schema
和基于@AspectJ
的AOP支持。下一章將討論較低級別的AOP支持。
AOP在Spring框架中用于:
提供聲明式企業(yè)服務(wù)。此類服務(wù)中最重要的是聲明式事務(wù)管理。
讓用戶實現(xiàn)自定義切面,并用AOP補充其對OOP的使用。
如果你只對通用聲明性服務(wù)或其他預(yù)包裝的聲明性中間件服務(wù)(例如池)感興趣,則無需直接使用Spring AOP,并且可以跳過本章的大部分內(nèi)容。
讓我們首先定義一些主要的AOP概念和術(shù)語。這些術(shù)語不是特定于Spring的。不幸的是,AOP術(shù)語并不是特別直觀。但是,如果使用Spring自己的術(shù)語,將會更加令人困惑。
切面:涉及多個類別的關(guān)注點的模塊化。事務(wù)管理是企業(yè)Java應(yīng)用程序中橫切關(guān)注的一個很好的例子。在Spring AOP中,切面是通過使用常規(guī)類(基于schema的方法)或使用@Aspect
注解(@AspectJ樣式)注釋的常規(guī)類來實現(xiàn)的。
連接點:程序執(zhí)行過程中的一點,例如方法的執(zhí)行或異常的處理。在Spring AOP中,連接點始終代表方法的執(zhí)行。
通知:切面在特定的連接點處采取的操作。不同類型的通知包括:“around
”,“before
”和“after
”通知。(通知類型將在后面討論。)包括Spring在內(nèi)的許多AOP框架都將通知建模為攔截器,并在連接點周圍維護(hù)一系列攔截器。
切入點:表示匹配連接點。通知與切入點表達(dá)式關(guān)聯(lián),并在與該切入點匹配的任何連接點處運行(例如,執(zhí)行具有特定名稱的方法)。切入點表達(dá)式匹配的連接點的概念是AOP的核心,默認(rèn)情況下,Spring使用AspectJ
切入點表達(dá)語言。
引入:在類型上聲明其他方法或字段。Spring AOP允許你向任何通知對象引入新的接口(和相應(yīng)的實現(xiàn))。例如,你可以使用引入使Bean實現(xiàn)IsModified接口,以簡化緩存。(引入在AspectJ
社區(qū)中稱為類型間聲明。)
目標(biāo)對象:一個或多個切面通知的對象。也稱為“通知對象”。由于Spring AOP是使用運行時代理實現(xiàn)的,因此該對象始終是代理對象。
AOP代理:由AOP框架創(chuàng)建的對象,用于實現(xiàn)切面約定(通知方法執(zhí)行等)。在Spring Framework中,AOP代理是JDK
動態(tài)代理或CGLIB
代理。
編織:將切面與其他應(yīng)用程序類型或?qū)ο箧溄右詣?chuàng)建通知的對象。這可以在編譯時(例如,使用AspectJ
編譯器),加載時或在運行時完成。像其他純Java AOP框架一樣,Spring AOP在運行時執(zhí)行編織。
Spring AOP包括以下類型的通知:
前置通知:在連接點之前運行但無法阻止執(zhí)行流前進(jìn)到連接點的通知(除非它引發(fā)異常)。
后置通知:連接點正常完成后要運行的通知(例如,如果某個方法返回而沒有引發(fā)異常)。
后置異常通知:如果方法因拋出異常而退出,將執(zhí)行的通知。
最終通知:無論連接點退出的方式如何(正常或異常返回),都將執(zhí)行通知。
環(huán)繞通知:圍繞連接點的通知,例如方法調(diào)用。這是最強大的通知。環(huán)繞通知可以在方法調(diào)用之前和之后執(zhí)行自定義行為。它還負(fù)責(zé)選擇是繼續(xù)到連接點,還是通過返回自己的返回值或拋出異常來簡化通知的方法執(zhí)行。
環(huán)繞通知是最通用的通知。由于Spring AOP與AspectJ
一樣,提供了各種通知類型,因此我們建議你使用功能最弱的建議類型,以實現(xiàn)所需的行為。例如,如果你只需要使用方法的返回值更新緩存,則最好使用后置通知而不是環(huán)繞通知,盡管環(huán)繞通知可以完成相同的事情。使用最具體的通知類型可提供更簡單的編程模型,并減少出錯的可能性。例如,你不需要在用于環(huán)繞通知的JoinPoint
上調(diào)用proceed()
方法,因此,你不會失敗。
所有通知參數(shù)都是靜態(tài)類型的,因此你可以使用適當(dāng)類型(例如,從方法執(zhí)行返回的值的類型)而不是對象數(shù)組的 通知參數(shù)。
切入點匹配的連接點的概念是AOP的關(guān)鍵,它與僅提供攔截功能的舊技術(shù)不同。切入點使通知的目標(biāo)獨立于面向?qū)ο蟮膶哟谓Y(jié)構(gòu)。例如,你可以將提供聲明性事務(wù)管理的環(huán)繞通知應(yīng)用于跨越多個對象(例在服務(wù)層中的所有業(yè)務(wù)操作)的一組方法。
Spring AOP是用純Java實現(xiàn)的。不需要特殊的編譯過程。Spring AOP不需要控制類加載器的層次結(jié)構(gòu),因此適合在Servlet容器或應(yīng)用程序服務(wù)器中使用。
Spring AOP當(dāng)前僅支持方法執(zhí)行連接點(通知在Spring Bean上執(zhí)行方法)。盡管可以在不破壞核心Spring AOP API的情況下添加對字段攔截的支持,但并未實現(xiàn)字段攔截。如果需要通知字段訪問和更新連接點,請考慮使用諸如AspectJ
之類的語言。
Spring AOP的AOP方法不同于大多數(shù)其他AOP框架。目的不是提供最完整的AOP實現(xiàn)(盡管Spring AOP相當(dāng)強大)。相反,其目的是在AOP實現(xiàn)和Spring IoC之間提供緊密的集成,以幫助解決企業(yè)應(yīng)用程序中的常見問題。
因此,例如,通常將Spring Framework的AOP功能與Spring IoC容器結(jié)合使用。通過使用常規(guī)bean定義語法來配置切面(盡管這允許強大的“自動代理”功能)。這是與其他AOP實現(xiàn)的關(guān)鍵區(qū)別。使用Spring AOP不能輕松或有效地完成一些事情,比如通知非常細(xì)粒度的對象(通常是域?qū)ο?。在這種情況下,AspectJ
是最佳選擇。但是,我們的經(jīng)驗是,Spring AOP為AOP可以解決的企業(yè)Java應(yīng)用程序中的大多數(shù)問題提供了出色的解決方案。
Spring AOP從未努力與AspectJ
競爭以提供全面的AOP解決方案。我們認(rèn)為,基于代理的框架(如Spring AOP)和成熟的框架(如AspectJ
)都是有價值的,它們是互補的,而不是競爭。Spring無縫地將Spring AOP和IoC與AspectJ
集成在一起,以在基于Spring的一致應(yīng)用程序架構(gòu)中支持AOP的所有功能。這種集成不會影響Spring AOP API或AOP Alliance
API。Spring AOP仍然向后兼容。請參閱下一章,以討論Spring AOP API。
Spring框架的中心宗旨之一是非侵入性。這就是不應(yīng)該強迫你將特定于框架的類和接口引入你的業(yè)務(wù)或領(lǐng)域模型的思想。但是,在某些地方,Spring Framework確實為你提供了將特定于Spring Framework的依賴項引入代碼庫的選項。提供此類選項的理由是,在某些情況下,以這種方式閱讀或編碼某些特定功能可能會變得更加容易。但是,Spring框架(幾乎)總是為你提供選擇:你可以自由地就哪個選項最適合你的特定用例或場景做出明智的決定。
與本章相關(guān)的一種選擇是選擇哪種AOP框架(以及哪種AOP樣式)。你可以選擇
AspectJ
和或Spring AOP。你也可以選擇@AspectJ
注解樣式方法或Spring XML配置樣式方法。本章選擇首先介紹@AspectJ
風(fēng)格的方法,這不能表明Spring比Spring XML配置風(fēng)格更喜歡@AspectJ
注釋風(fēng)格的方法(備注:使用AspectJ
編寫例子不能說明Spring更喜歡AspectJ
注解編程)。有關(guān)每種樣式的“來龍去脈”的更完整討論,請參見選擇要使用的AOP聲明樣式。
Spring AOP默認(rèn)將標(biāo)準(zhǔn)JDK動態(tài)代理用于AOP代理。這使得可以代理任何接口(或一組接口)。
Spring AOP也可以使用CGLIB代理。這對于代理類而不是接口是必需的。默認(rèn)情況下,如果業(yè)務(wù)對象未實現(xiàn)接口,則使用CGLIB。由于對接口而不是對類進(jìn)行編程是一種好習(xí)慣,因此業(yè)務(wù)類通常實現(xiàn)一個或多個業(yè)務(wù)接口。在某些情況下(可能極少發(fā)生),你需要通知在接口上未聲明的方法,或需要將代理對象作為具體類型傳遞給方法,則可以強制使用CGLIB。
掌握Spring AOP是基于代理的這一事實很重要。請參閱了解AOP代理以全面了解此實現(xiàn)細(xì)節(jié)的實際含義。
@AspectJ
是一種將切面聲明為帶有注解的常規(guī)Java類的樣式。@AspectJ
樣式是AspectJ項目在AspectJ 5版本中引入的。Spring使用AspectJ
提供的用于切入點解析和匹配的庫來解釋與AspectJ 5相同的注解。但是,AOP運行時仍然是純Spring AOP,并且不依賴于AspectJ
編譯器或編織器。
使用
AspectJ
編譯器和編織器可以使用完整的AspectJ
語言,有關(guān)在Spring Applications中使用AspectJ進(jìn)行了討論。
@AspectJ
支持
要在Spring配置中使用@AspectJ
切面,你需要啟用Spring支持以基于@AspectJ
切面配置Spring AOP,并根據(jù)這些切面是否通知對Bean進(jìn)行自動代理。通過自動代理,我們的意思是,如果Spring確定一個或多個切面通知一個bean,它會自動為該bean生成一個代理來攔截方法調(diào)用并確保按需執(zhí)行通知。
可以使用XML或Java樣式的配置來啟用@AspectJ
支持。無論哪種情況,你都需要確保AspectJ
的Aspectjweaver.jar
庫位于應(yīng)用程序的類路徑(版本1.8或更高版本)上。該庫在AspectJ
發(fā)行版的lib
目錄中或從Maven Central存儲庫中獲取。
通過Java配置激活@AspectJ
通過Java @Configuration
啟用@AspectJ
支持,請?zhí)砑?code>@EnableAspectJAutoProxy注解,如以下示例所示:
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
通過XML配置激活@AspectJ
通過基于XML的配置啟用@AspectJ
支持,請使用<aop:aspectj-autoproxy>
元素,如以下示例所示:
<aop:aspectj-autoproxy/>
假定你使用基于XML Schema的配置中所述的架構(gòu)支持。有關(guān)如何在aop名稱空間中導(dǎo)入標(biāo)簽的信息,請參見AOP schema。
啟用@AspectJ
支持后,Spring會自動檢測在應(yīng)用程序上下文中使用@AspectJ
切面(有@Aspect
注解)的類定義的任何bean,并用于配置Spring AOP。接下來的兩個示例顯示了一個不太有用的切面所需的最小定義。
兩個示例中的第一個示例顯示了應(yīng)用程序上下文中的常規(guī)bean定義,該定義指向具有@Aspect
注解的bean類:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> <!-- configure properties of the aspect here --> </bean>
這兩個示例中的第二個示例顯示了NotVeryUsefulAspect
類定義,該類定義使用org.aspectj.lang.annotation.Aspect
注解進(jìn)行注釋;
package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { }
切面(使用@Aspect
注解的類)可以具有方法和字段,與任何其他類相同。它們還可以包含切入點、通知和引入(類型間)聲明。
通過組件掃描自動檢測切面
你可以將切面類注冊為Spring XML配置中的常規(guī)bean,也可以通過類路徑掃描自動檢測它們-與其他任何Spring管理的bean一樣。但是,請注意,
@Aspect
注解不足以在類路徑中進(jìn)行自動檢測。為此,你需要添加一個單獨的@Component
注解(或者,按照Spring的組件掃描程序的規(guī)則,有條件的自定義構(gòu)造型注解)。向其他切面提供通知?
在Spring AOP中,切面本身不能成為其他切面的通知目標(biāo)。類上的
@Aspect
注解將其標(biāo)記為一個切面,因此將其從自動代理中排除。
切入點確定了感興趣的連接點,從而使我們能夠控制何時執(zhí)行通知。Spring AOP僅支持Spring Bean的方法執(zhí)行連接點,因此你可以將切入點視為與Spring Bean上的方法執(zhí)行匹配。切入點聲明由兩部分組成:一個包含名稱和任何參數(shù)的簽名,以及一個切入點表達(dá)式,該表達(dá)式精確確定我們感興趣的方法執(zhí)行。在AOP的@AspectJ
注解樣式中,常規(guī)方法定義提供了切入點簽名,并且使用@Pointcut
注解指示了切入點表達(dá)式(用作切入點簽名的方法必須具有void
返回類型)。一個示例可能有助于使切入點簽名和切入點表達(dá)式之間的區(qū)別變得清晰。下面的示例定義一個名為anyOldTransfer
的切入點,該切入點與任何名為transfer
方法的執(zhí)行相匹配:
@Pointcut("execution(* transfer(..))") // 切入點表達(dá)式 private void anyOldTransfer() {} // 切入點方法簽名
形成@Pointcut
注解的值的切入點表達(dá)式是一個常規(guī)的AspectJ 5切入點表達(dá)式。
支持的切入點指示符
Spring AOP支持以下在切入點表達(dá)式中使用的AspectJ
<u>切入點指示符(PCD)</u>:
execution
: 用于匹配方法執(zhí)行的連接點。這是使用Spring AOP時要使用的主要切入點指示符。
within
: 限制對某些類型內(nèi)的連接點的匹配(使用Spring AOP時在匹配類型內(nèi)聲明的方法的執(zhí)行)。
this
:限制匹配到連接點(使用Spring AOP時方法的執(zhí)行)的匹配,其中bean引用(Spring AOP代理)是給定類型的實例。
target
: 限制匹配到連接點(使用Spring AOP時方法的執(zhí)行)的匹配,其中目標(biāo)對象(代理的應(yīng)用程序?qū)ο螅┦墙o定類型的實例。
args
: 限制匹配到連接點(使用Spring AOP時方法的執(zhí)行)的匹配,其中參數(shù)是給定類型的實例。
@target
: 限制匹配到連接點(使用Spring AOP時方法的執(zhí)行)的匹配,其中執(zhí)行對象的類具有給定類型的注釋。
@args
:限制匹配的連接點(使用Spring AOP時方法的執(zhí)行),其中傳遞的實際參數(shù)的運行時類型具有給定類型的注解。
@within
:限制匹配到具有給定注解的類型中的連接點(使用Spring AOP時,使用給定注解在類型中聲明的方法的執(zhí)行)。
@annotation
: 將匹配點限制在連接點的主題(Spring AOP中正在執(zhí)行的方法)具有給定注解的連接點。
其他切入點
完整的
AspectJ
切入點語言支持Spring不支持的其他切入點指示符:call
,get
,set
,preinitialization
,staticinitialization
,initialization
,handler
,adviceexecution
,withincode
,cflow
,cflowbelow
,if
,@this
和@withincode
(備注:意思是Spring不支持這些指示符)。在Spring AOP解釋的切入點表達(dá)式中使用這些切入點指示符會導(dǎo)致拋出IllegalArgumentException
。Spring AOP支持的切入點指示符集合可能會在將來的版本中擴(kuò)展,以支持更多的
AspectJ
切入點指示符。
由于Spring AOP僅將匹配限制為僅方法執(zhí)行連接點,因此前面對切入點指示符的討論所給出的定義比在AspectJ
編程指南中所能找到的要窄。此外,AspectJ
本身具有基于類型的語義,并且在執(zhí)行連接點處,this
和target
都引用同一個對象:執(zhí)行該方法的對象。Spring AOP是基于代理的系統(tǒng),可區(qū)分代理對象本身(綁定到此對象)和代理背后的目標(biāo)對象(綁定到目標(biāo))。
由于Spring的AOP框架基于代理的性質(zhì),因此根據(jù)定義,不會攔截目標(biāo)對象內(nèi)的調(diào)用。對于JDK代理,只能攔截代理上的公共接口方法調(diào)用。使用CGLIB,將攔截代理上的公共方法和受保護(hù)的方法調(diào)用(必要時甚至包可見的方法)。但是,通常應(yīng)通過公共簽名設(shè)計通過代理進(jìn)行的常見交互。
請注意,切入點定義通常與任何攔截方法匹配。如果嚴(yán)格地將切入點設(shè)置為僅公開使用,即使在CGLIB代理方案中通過代理可能存在非公開交互,也需要相應(yīng)地進(jìn)行定義。
如果你的攔截需要在目標(biāo)類中包括方法調(diào)用甚至構(gòu)造函數(shù),請考慮使用Spring驅(qū)動的本地AspectJ編織,而不是Spring的基于代理的AOP框架。這構(gòu)成了具有不同特征的AOP使用模式,因此在做出決定之前一定要熟悉編織。
Spring AOP還支持其他名為bean的PCD。使用PCD,可以將連接點的匹配限制為特定的命名Spring Bean或一組命名Spring Bean(使用通配符時)。Bean PCD具有以下形式:
bean(idOrNameOfBean)
idOrNameOfBean
標(biāo)記可以是任何Spring bean的名稱。提供了使用*
字符的有限通配符支持,因此,如果為Spring bean建立了一些命名約定,則可以編寫bean PCD表達(dá)式來選擇它們。與其他切入點指示符一樣,bean PCD可以與&&
(和)、||
(或)、和!
(否定)運算符一起使用。
Bean PCD僅在Spring AOP中受支持,而在本地
AspectJ
編織中不受支持。它是AspectJ
定義的標(biāo)準(zhǔn)PCD的特定于Spring的擴(kuò)展,因此不適用于@Aspect
模型中聲明的切面。Bean PCD在實例級別(基于Spring bean名稱概念構(gòu)建)上運行,而不是僅在類型級別(基于編織的AOP受其限制)上運行?;趯嵗那腥朦c指示符是Spring基于代理的AOP框架的特殊功能,并且與Spring bean工廠緊密集成,因此可以自然而直接地通過名稱識別特定bean。
組合切入點表達(dá)式
你可以使用&&
、||
和!
組合切入點表達(dá)式。你還可以按名稱引用切入點表達(dá)式。以下示例顯示了三個切入點表達(dá)式:
@Pointcut("execution(public * *(..))") private void anyPublicOperation() {} //1 @Pointcut("within(com.xyz.someapp.trading..*)") private void inTrading() {} //2 @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {} //3
如果方法執(zhí)行連接點表示任何公共方法的執(zhí)行,則anyPublicOperation
匹配。
如果交易模塊中有方法執(zhí)行,則inTrading
匹配。
如果方法執(zhí)行代表交易模塊中的任何公共方法,則tradingOperation
匹配。
最佳實踐是從較小的命名組件中構(gòu)建更復(fù)雜的切入點表達(dá)式,如先前所示。按名稱引用切入點時,將應(yīng)用常規(guī)的Java可見性規(guī)則(你可以看到相同類型的private切入點,層次結(jié)構(gòu)中protected
的切入點,任何位置的public切入點,等等)??梢娦圆挥绊懬腥朦c匹配。
共享通用切入點定義
在企業(yè)級應(yīng)用中,開發(fā)人員通常希望從多個方面引用應(yīng)用程序的模塊和特定的操作集。我們建議為此定義一個 SystemArchitecture
切面,以捕獲常見的切入點表達(dá)式意圖。這樣的切面通常類似于以下示例:
package com.xyz.someapp; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class SystemArchitecture { /** * A join point is in the web layer if the method is defined * in a type in the com.xyz.someapp.web package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} /** * A join point is in the service layer if the method is defined * in a type in the com.xyz.someapp.service package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.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.someapp.dao package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.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.someapp.abc.service and com.xyz.someapp.def.service) then * the pointcut expression "execution(* com.xyz.someapp..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.someapp..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.someapp.dao.*.*(..))") public void dataAccessOperation() {} }
你可以在需要切入點表達(dá)式的任何地方引用在此切面定義的切入點。例如,要使服務(wù)層具有事務(wù)性,你可以編寫以下內(nèi)容:
<aop:config> <aop:advisor pointcut="com.xyz.someapp.SystemArchitecture.businessService()" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
在基于schema的AOP支持中討論了<aop:config>
和<aop:advisor>
元素。事務(wù)管理中討論了事務(wù)元素。
實例
Spring AOP用戶可能最常使用execution
切入點指示符。執(zhí)行表達(dá)式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除了返回類型模式(前面的代碼片段中的ret-type-pattern
),名稱模式(name-pattern
)和參數(shù)模式(param-pattern
)以外的所有部分都是可選的。返回類型模式確定要匹配連接點、方法的返回類型必須是什么。最常用作返回類型模式。它匹配任何返回類型。僅當(dāng)方法返回給定類型時,標(biāo)準(zhǔn)類型名稱才匹配。名稱模式與方法名稱匹配。你可以將通配*
符用作名稱模式的全部或一部分。如果你指定了聲明類型模式,請在其后加上.
將其加入名稱模式組件。參數(shù)模式稍微復(fù)雜一些:()
匹配不帶參數(shù)的方法,而(..)
匹配任意數(shù)量(零個或多個)的參數(shù)。(*)
模式與采用任何類型的一個參數(shù)的方法匹配。(*,String)
與采用兩個參數(shù)的方法匹配。第一個可以是任何類型,而第二個必須是字符串。有關(guān)更多信息,請查閱AspectJ編程指南的“語言語義”部分。
以下示例顯示了一些常用的切入點表達(dá)式:
任何公共方法的執(zhí)行:
execution(public * *(..))
名稱以set
開頭的任何方法的執(zhí)行:
execution(* set*(..))
AccountService
接口定義的任何方法的執(zhí)行:
execution(* com.xyz.service.AccountService.*(..))
service
包中定義的任何方法的執(zhí)行:
execution(* com.xyz.service. * . * (..))
service
包或其子包之一中定義的任何方法的執(zhí)行:
execution(* com.xyz.service . . * . *(..))
service
包中的任何連接點(僅在Spring AOP中執(zhí)行方法):
within(com.xyz.service.*)
service
包或其子包之一中的任何連接點(僅在Spring AOP中執(zhí)行方法):
within(com.xyz.service..*)
代理實現(xiàn)AccountService
接口的任何連接點(僅在Spring AOP中執(zhí)行方法):
this(com.xyz.service.AccountService)
this
通常以綁定形式使用。有關(guān)如何在通知正文中使代理對象可用的信息,請參閱“聲明通知”部分
目標(biāo)對象實現(xiàn)AccountService
接口的任何連接點(僅在Spring AOP中執(zhí)行方法):
target(com.xyz.service.AccountService)
target
通常以綁定形式使用。有關(guān)如何使目標(biāo)對象在建議正文中可用的信息,請參見“聲明通知”部分。
任何采用單個參數(shù)并且在運行時傳遞的參數(shù)為Serializable
的連接點(僅在Spring AOP中執(zhí)行方法):
args(java.io.Serializable)
args
通常以綁定形式使用。有關(guān)如何使方法參數(shù)在通知正文中可用的信息,請參見“聲明通知”部分。
請注意,此示例中給出的切入點與execution(* *(java.io.Serializable))
不同。如果在運行時傳遞的參數(shù)為Serializable,則args版本匹配;如果方法簽名聲明一個類型為Serializable的參數(shù),則執(zhí)行版本匹配。
目標(biāo)對象具有@Transactional
注解的任何連接點(僅在Spring AOP中方法執(zhí)行):
@target(org.springframework.transaction.annotation.Transactional)
你也可以在綁定形式中使用
@target
。有關(guān)如何使注解對象在建議正文中可用的信息,請參見“聲明通知”部分。
目標(biāo)對象的聲明類型具有@Transactional
注解的任何連接點(僅在Spring AOP中方法執(zhí)行):
@within(org.springframework.transaction.annotation.Transactional)
你也可以在綁定形式中使用
@within
。有關(guān)如何使注解對象在通知正文中可用的信息,請參見“聲明通知”部分。
任何執(zhí)行方法帶有@Transactional
注解的連接點(僅在Spring AOP中是方法執(zhí)行):
@annotation(org.springframework.transaction.annotation.Transactional)
你也可以在綁定形式中使用
@annotation
。有關(guān)如何使注解對象在通知正文中可用的信息,請參見“聲明通知”部分。
任何采用單個參數(shù)的連接點(僅在Spring AOP中是方法執(zhí)行),并且傳遞的參數(shù)的運行時類型具有@Classified
注解:
@args(com.xyz.security.Classified)
你也可以在綁定形式中使用
@args
。請參閱“聲明通知”部分,如何使通知對象中的注解對象可用。
名為tradeService
的Spring bean上的任何連接點(僅在Spring AOP中執(zhí)行方法):
bean(tradeService)
Spring Bean上具有與通配符表達(dá)式* Service
匹配的名稱的任何連接點(僅在Spring AOP中才執(zhí)行方法):
bean(*Service)
寫一個好的連接點
在編譯期間,AspectJ
處理切入點以優(yōu)化匹配性能。檢查代碼并確定每個連接點是否(靜態(tài)或動態(tài))匹配給定的切入點是一個耗時的過程。(動態(tài)匹配意味著無法從靜態(tài)分析中完全確定匹配,并且在代碼中進(jìn)行了測試以確定在運行代碼時是否存在實際匹配)。首次遇到切入點聲明時,AspectJ
將其重寫為匹配過程的最佳形式。這是什么意思?基本上,切入點以DNF(析取范式)重寫,并且對切入點的組件進(jìn)行排序,以便首先檢查那些較便宜(消耗最小)的組件。這意味著你不必?fù)?dān)心理解各種切入點指示符的性能,并且可以在切入點聲明中以任何順序提供它們。
但是,AspectJ
只能使用所告訴的內(nèi)容。為了獲得最佳的匹配性能,你應(yīng)該考慮他們試圖達(dá)到的目標(biāo),并在定義中盡可能縮小匹配的搜索空間?,F(xiàn)有的指示符自然分為三類之一:同類、作用域和上下文:
Kinded
指示器選擇特定類型的連接點:execution
、 get
、 set
、call
和 handler
。
Scoping
指示器選擇一組感興趣的連接點(可能是多種類型的):within
和withincode
Contextual
指示符根據(jù)上下文匹配(和可選綁定):this
、target
和@annotation
編寫正確的切入點至少應(yīng)包括前兩種類型(Kinded
和Scoping
)。你可以包括上下文指示符以根據(jù)連接點上下文進(jìn)行匹配,也可以綁定該上下文以在通知中使用。僅提供Kinded
的標(biāo)識符或僅提供Contextual
的標(biāo)識符是可行的,但是由于額外的處理和分析,可能會影響編織性能(使用的時間和內(nèi)存)。Scoping
指定符的匹配非???,使用它們意味著AspectJ
可以非常迅速地消除不應(yīng)進(jìn)一步處理的連接點組。一個好的切入點應(yīng)盡可能包括一個切入點。
參考代碼:
com.liyong.ioccontainer.starter.AopIocContiner
通知與切入點表達(dá)式關(guān)聯(lián),并且在切入點匹配的方法執(zhí)行之前、之后或周圍運行。切入點表達(dá)式可以是對命名切入點的簡單引用,也可以是在適當(dāng)位置聲明的切入點表達(dá)式。
前置通知
你可以使用@Before
注解在一個切面中聲明前置通知:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
如果使用就地切入點表達(dá)式,則可以將前面的示例重寫為以下示例:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeExample { @Before("execution(* com.xyz.myapp.dao.*.*(..))") public void doAccessCheck() { // ... } }
返回通知
在當(dāng)匹配方法正常的執(zhí)行返回時,返回通知運行。你可以使用@AfterReturning
注解進(jìn)行聲明:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } }
你可以在同一切面內(nèi)擁有多個通知聲明(以及其他成員)。在這些示例中,我們僅顯示單個通知聲明,以及其中每個通知的效果。
有時,你需要在通知正文中訪問返回的實際值。你可以使用@AfterReturning
的形式綁定返回值以獲取該訪問,如以下示例所示:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterReturning; @Aspect public class AfterReturningExample { @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // ... } }
返回屬性中使用的名稱必須與advice
方法中的參數(shù)名稱相對應(yīng)。當(dāng)方法執(zhí)行返回時,返回值將作為相應(yīng)的參數(shù)值傳遞到通知方法。returning
也將匹配限制為僅返回指定類型值的方法執(zhí)行(在這種情況下為Object,它匹配任何返回值)。
請注意,當(dāng)使用返回后通知時,不可能返回完全不同的引用。
異常后置通知
在拋異常通知后,當(dāng)匹配的方法執(zhí)行通過拋出異常退出時運行。你可以使用@AfterThrowing
注解進(jìn)行聲明,如以下示例所示:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doRecoveryActions() { // ... } }
通常,你希望通知僅在引發(fā)給定類型的異常時才運行,并且你通常還需要訪問通知正文中的引發(fā)異常。你可以使用throwing
屬性來限制匹配(如果需要)(否則,請使用Throwable
作為異常類型),并將拋出的異常綁定到通知的參數(shù)。以下示例顯示了如何執(zhí)行此操作:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; @Aspect public class AfterThrowingExample { @AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... } }
throwing
屬性中使用的名稱必須與通知方法中的參數(shù)名稱相對應(yīng)。當(dāng)通過拋出異常退出方法執(zhí)行時,該異常將作為相應(yīng)的參數(shù)值傳遞給通知的方法。throwing
還將匹配僅限制為拋出指定類型的異常(在這種情況下為DataAccessException
)的方法執(zhí)行。
最終通知
當(dāng)匹配的方法執(zhí)行退出時,通知(最終)運行。通過使用@After
注解聲明它。之后必須準(zhǔn)備處理正常和異常返回條件的通知。它通常用于釋放資源和類似目的。以下示例顯示了最終通知的用法:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After; @Aspect public class AfterFinallyExample { @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doReleaseLock() { // ... } }
環(huán)繞通知
最后一種通知是環(huán)繞通知。環(huán)繞通知在匹配方法的執(zhí)行過程中“環(huán)繞”運行。它有機(jī)會在方法執(zhí)行之前和之后執(zhí)行工作,并確定何時、如何執(zhí)行,甚至是否真的執(zhí)行方法。如果需要以線程安全的方式(例如,啟動和停止計時器)在方法執(zhí)行之前和之后共享狀態(tài),則通常使用環(huán)繞通知。始終使用能力最小的通知來滿足你的要求(也就是說,在通知可以使前置通知時,請勿用環(huán)繞通知)。
通過使用@Around
注解來聲明環(huán)繞通知。通知方法的第一個參數(shù)必須是ProceedingJoinPoint
類型。在通知的正文中,在ProceedingJoinPoint
上調(diào)用proceed()
會使底層(真正的執(zhí)行方法)方法執(zhí)行。proceed
方法也可以傳入Object []
。數(shù)組中的值用作方法執(zhí)行時的參數(shù)。
當(dāng)用
Object []
進(jìn)行調(diào)用時,proceed
的行為與AspectJ
編譯器所編譯的around
通知的proceed
為略有不同。對于使用傳統(tǒng)AspectJ
語言編寫的環(huán)繞通知,傳遞給proceed
的參數(shù)數(shù)量必須與傳遞給環(huán)繞通知的參數(shù)數(shù)量(而不是基礎(chǔ)連接點采用的參數(shù)數(shù)量)相匹配,并且傳遞給給定的參數(shù)位置會取代該值綁定到的實體的連接點處的原始值(不要擔(dān)心,如果這現(xiàn)在沒有意義)。Spring采取的方法更簡單,并且更適合其基于代理的,僅執(zhí)行的語義。如果你編譯為Spring編寫的@AspectJ
切面,并在AspectJ
編譯器和weaver
中使用參數(shù)進(jìn)行處理,則只需要意識到這種區(qū)別。有一種方法可以在Spring AOP和AspectJ
之間100%兼容,并且在下面有關(guān)通知參數(shù)的部分中對此進(jìn)行了討論。
以下示例顯示了如何使用環(huán)繞通知:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } }
環(huán)繞通知返回的值是該方法的調(diào)用者看到的返回值。例如,如果一個簡單的緩存切面有一個值,則它可以從緩存中返回一個值,如果沒有,則調(diào)用proceed()
。請注意,在環(huán)繞通知的正文中,proceed
可能被調(diào)用一次,多次或完全不被調(diào)用。所有這些都是合法的。
通知參數(shù)
Spring提供了完全類型化的通知,這意味著你可以在通知簽名中聲明所需的參數(shù)(如我們先前在返回和拋出示例中所看到的),而不是一直使用Object []
數(shù)組。我們將在本節(jié)的后面部分介紹如何使參數(shù)和其他上下文值可用于通知主體。首先,我們看一下如何編寫通用通知,以了解該通知當(dāng)前通知的方法。
獲取當(dāng)前JoinPoint
任何通知方法都可以將org.aspectj.lang.JoinPoint
類型的參數(shù)聲明為其第一個參數(shù)。請注意,環(huán)繞通知聲明ProceedingJoinPoint
類型為第一個參數(shù),該參數(shù)是JoinPoint
的子類。JoinPoint
接口提供了許多有用的方法:
getArgs()
: 返回方法參數(shù)。
getThis()
: 返回代理對象。
getTarget()
: 返回目標(biāo)對象。
getSignature()
: 返回通知使用的方法的描述。
toString()
: 打印有關(guān)所有通知方法的有用描述。
有關(guān)更多詳細(xì)信息,請參見javadoc。
傳遞參數(shù)給通知
我們已經(jīng)看到了如何綁定返回的值或異常值(在返回之后和引發(fā)通知之后)。要使參數(shù)值可用于通知正文,可以使用args
的綁定形式。如果在args
表達(dá)式中使用參數(shù)名稱代替類型名稱,則在調(diào)用通知時會將相應(yīng)參數(shù)的值作為參數(shù)值傳遞。一個例子應(yīng)該使這一點更清楚。假設(shè)你要通知以Account
對象作為第一個參數(shù)的DAO
操作的執(zhí)行,并且你需要在通知正文中訪問該帳戶。你可以編寫以下內(nèi)容:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") public void validateAccount(Account account) { // ... }
切入點表達(dá)式的args(account,..)
部分有兩個用途。首先,它將匹配限制為僅方法采用至少一個參數(shù)且傳遞給該參數(shù)的參數(shù)為Account
實例的那些方法執(zhí)行。其次,它通過account
參數(shù)使實際的Account
對象可用于通知。
寫這個的另一種方法是聲明一個切入點,當(dāng)它匹配一個連接點時提供Account
對象值,然后從通知中引用命名的切入點。如下所示:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account) {} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account) { // ... }
代理對象(this
)、目標(biāo)對象(target
)和注解(@within
,@target
,@annotation
和@args
)都可以以類似的方式綁定。接下來的兩個示例顯示如何匹配使用@Auditable
注解的方法的執(zhí)行并提取審計代碼:
這兩個示例中的第一個顯示了@Auditable
注解的定義:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); }
這兩個示例中的第二個示例顯示了與@Auditable
方法的執(zhí)行相匹配的通知:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)") public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
通知參數(shù)和泛型
Spring AOP可以處理類聲明和方法參數(shù)中使用的泛型。假設(shè)你具有如下通用類型:
public interface Sample<T> { void sampleGenericMethod(T param); void sampleGenericCollectionMethod(Collection<T> param); }
你可以通過在要攔截方法的參數(shù)類型中鍵入advice參數(shù),將方法類型的攔截限制為某些參數(shù)類型:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation }
這種方法不適用于泛型集合。因此,你不能按以下方式定義切入點:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") public void beforeSampleMethod(Collection<MyType> param) { // Advice implementation }
為了使這項工作有效,我們將不得不檢查集合的每個元素,這是不合理的,因為我們也無法決定通常如何處理null
。要實現(xiàn)類似的目的,你必須將參數(shù)鍵入Collection <?>
并手動檢查元素的類型。
確定參數(shù)名稱
通知調(diào)用中的參數(shù)綁定依賴于切入點表達(dá)式中使用的名稱與通知和切入點方法簽名中聲明的參數(shù)名稱的匹配。
通過Java反射無法獲得參數(shù)名稱,因此Spring AOP使用以下策略來確定參數(shù)名稱:
如果用戶已明確指定參數(shù)名稱,則使用指定的參數(shù)名稱。通知和切入點注解均具有可選的argNames
屬性,你可以使用該屬性來指定帶注解的方法的參數(shù)名稱。這些參數(shù)名稱在運行時可用。以下示例顯示如何使用argNames
屬性:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code and bean }
如果第一個參數(shù)是JoinPoint
、ProceedingJoinPoint
或JoinPoint.StaticPart
類型,則可以從argNames
屬性的值中忽略該參數(shù)的名稱。例如,如果你修改前面的通知以接收連接點對象,則argNames
屬性不需要包括它:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(JoinPoint jp, Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code, bean, and jp }
對JoinPoint
、ProceedingJoinPoint
和JoinPoint.StaticPart
類型的第一個參數(shù)給予的特殊處理對于不收集任何其他連接點上下文的通知實例特別方便。在這種情況下,你可以省略argNames
屬性。例如,以下通知無需聲明argNames
屬性:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp }
使用'argNames
'屬性有點笨拙,因此,如果未指定'argNames
'屬性,Spring AOP將查找該類的調(diào)試信息,并嘗試從局部變量表中確定參數(shù)名稱。只要已使用調(diào)試信息(至少是 -g:vars
)編譯了類,此信息就會存在。 啟用此標(biāo)志時進(jìn)行編譯的后果是:(1)你的代碼稍微易于理解(逆向工程),(2)類文件的大小略大(通常無關(guān)緊要),(3)編譯器未應(yīng)用刪除未使用的局部變量的優(yōu)化。換句話說,通過啟用該標(biāo)志,你應(yīng)該不會遇到任何困難。
如果即使沒有調(diào)試信息,
AspectJ
編譯器(ajc)都已編譯@AspectJ
切面,則無需添加argNames
屬性,因為編譯器會保留所需的信息。
如果在沒有必要調(diào)試信息的情況下編譯了代碼,Spring AOP將嘗試推斷綁定變量與參數(shù)的配對(例如,如果切入點表達(dá)式中僅綁定了一個變量,并且advice方法僅接受一個參數(shù),則配對很明顯)。如果在給定可用信息的情況下變量的綁定不明確,則拋出AmbiguousBindingException
。
如果以上所有策略均失敗,則拋出IllegalArgumentException
。
proceed參數(shù)
前面我們提到過,我們將描述如何編寫一個在Spring AOP和AspectJ
中始終有效的參數(shù)的proceed
調(diào)用。解決方案是確保通知簽名按順序綁定每個方法參數(shù)。以下示例顯示了如何執(zhí)行此操作:
@Around("execution(List<Account> find*(..)) && " + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern}); }
在許多情況下,無論如何都要進(jìn)行此綁定(如上例所示)。
通知順序
當(dāng)多條通知都希望在同一連接點上運行時會發(fā)生什么? Spring AOP遵循與AspectJ
相同的優(yōu)先級規(guī)則來確定通知執(zhí)行的順序。優(yōu)先級最高的通知在進(jìn)入時首先運行(因此,給定兩個before
通知,優(yōu)先級最高的通知首先運行)。在從連接點出來的過程中,優(yōu)先級最高的通知最后運行(因此,給定兩個after
通知,優(yōu)先級最高的通知將排在第二)。
在不同切面定義的兩個通知都需要在同一個連接點上運行時,除非另行指定,否則執(zhí)行順序是未定義的。你可以通過指定優(yōu)先級來控制執(zhí)行順序。通過在切面類中實現(xiàn)org.springframework.core.Ordered
接口或使用Order注解對其進(jìn)行注解,可以通過常規(guī)的Spring方法來完成。給定兩個切面,從Ordered.getValue()
(或注解值)返回較低值的切面具有較高的優(yōu)先級
當(dāng)在同一個切面中定義的兩個通知都需要在同一個連接點上運行時,順序是未定義的(因為無法通過java編譯類的反射檢索聲明順序)??紤]將此類通知方法分解為每個切面類中的每個連接點的一個通知方法,或者將通知片段重構(gòu)為可以在切面級別排序的單獨切面類。
引入(在AspectJ中稱為類型間聲明)使能夠聲明已通知的對象實現(xiàn)給定接口,并代表這些對象提供該接口的實現(xiàn)。
你可以使用@DeclareParents
注解進(jìn)行介紹。此注解用于聲明匹配類型具有新的父類(因此具有名稱)。例如,給定一個名為UsageTracked
的接口和該接口的一個名為DefaultUsageTracked
的實現(xiàn),下面的切面聲明了服務(wù)接口的所有實現(xiàn)者也實現(xiàn)了UsageTracked
接口(例如通過JMX
公開統(tǒng)計信息):
@Aspect public class UsageTracking { @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } }
要實現(xiàn)的接口由帶注解的字段的類型確定。@DeclareParents
注解的value
屬性是AspectJ
類型的模式。匹配類型的任何bean都實現(xiàn)UsageTracked
接口。注意,在前面示例的before
通知中,服務(wù)bean可以直接用作UsageTracked
接口的實現(xiàn)。如果以編程方式訪問bean,則應(yīng)編寫以下內(nèi)容:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
參考代碼:
com.liyong.ioccontainer.starter.AopDeclareParentsIocContiner
這是一個高級主題。如果你剛開始使用AOP,則可以放心地跳過它,直到以后。
默認(rèn)情況下,應(yīng)用程序上下文中每個切面都有一個實例。 AspectJ將此稱為單例實例化模型??梢允褂胋ean生命周期來定義切面。Spring支持AspectJ的perthis
和pertarget
實例化模型(當(dāng)前不支持percflow
,percflowbelow
和pertypewithin
)。
你可以通過在@Aspect
注解中指定perthis
來聲明perthis
切面。考慮以下示例:
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect { private int someState; @Before(com.xyz.myapp.SystemArchitecture.businessService()) public void recordServiceUsage() { // ... } }
在前面的示例中,“ perthis
”子句的作用是為每個執(zhí)行業(yè)務(wù)服務(wù)的唯一服務(wù)對象(每個與切入點表達(dá)式匹配的連接點綁定到“ this
”的唯一對象)創(chuàng)建一個切面實例。切面實例是在服務(wù)對象上首次調(diào)用方法時創(chuàng)建的。當(dāng)服務(wù)對象超出范圍時,切面將超出范圍。在創(chuàng)建切面實例之前,其中的任何通知都不會執(zhí)行。一旦創(chuàng)建了切面實例,在其中聲明的通知就會在匹配的連接點上執(zhí)行,但僅當(dāng)服務(wù)對象與此切面相關(guān)聯(lián)時才執(zhí)行。有關(guān)每個子句的更多信息,請參見AspectJ編程指南。
pertarget
實例化模型的工作方式與perthis
完全相同,但是它在匹配的連接點為每個唯一目標(biāo)對象創(chuàng)建一個切面實例。
現(xiàn)在你已經(jīng)了解了所有組成部分是如何工作的,我們可以將它們組合在一起做一些有用的事情。
有時由于并發(fā)問題(例如,死鎖失?。瑯I(yè)務(wù)服務(wù)的執(zhí)行可能會失敗。如果重試該操作,則很可能在下一次嘗試中成功。對于適合在這種情況下重試的業(yè)務(wù)服務(wù)(不需要為解決沖突而需要返回給用戶的冪等操作),我們希望透明地重試該操作,以避免客戶端看到PessimisticLockingFailureException
。這是一個明顯跨越服務(wù)層中的多個服務(wù)的需求,因此非常適合通過切面實現(xiàn)。
因為我們想重試該操作,所以我們需要使用環(huán)繞通知,以便可以多次調(diào)用proceed
。以下清單顯示了基本切面的實現(xiàn):
@Aspect public class ConcurrentOperationExecutor implements Ordered { private static final int DEFAULT_MAX_RETRIES = 2; private int maxRetries = DEFAULT_MAX_RETRIES; private int order = 1; public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; PessimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch(PessimisticLockingFailureException ex) { lockFailureException = ex; } } while(numAttempts <= this.maxRetries); throw lockFailureException; } }
請注意,切面實現(xiàn)了Ordered
接口,以便我們可以將切面的優(yōu)先級設(shè)置為高于事務(wù)通知的優(yōu)先級(每次重試時都需要一個新的事務(wù))。maxRetries
和order
屬性均由Spring配置。通知的主要動作發(fā)生在doConcurrentOperation
中。請注意,目前,我們將重試邏輯應(yīng)用于每個businessService()
。我們嘗試?yán)^續(xù),如果失敗并出現(xiàn)PessimisticLockingFailureException
,則我們將再次重試,除非我們用盡了所有重試嘗試。
對應(yīng)的Spring配置如下:
<aop:aspectj-autoproxy/> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
為了完善切面,使其僅重試冪等操作,我們可以定義以下冪等注解:
@Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation }
然后,我們可以使用注解來注釋服務(wù)操作的實現(xiàn)。切面更改為僅重試冪等操作涉及更改切入點表達(dá)式,以便僅@Idempotent
操作匹配,如下所示:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { // ... }
“如何激活@AspectJ支持”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。