?
? ????? PHP ??? ???? ??? ?? ??
目錄
面向切面編程(AOP)通過(guò)提供另外一種思考程序結(jié)構(gòu)的途經(jīng)來(lái)彌補(bǔ)面向?qū)ο缶幊蹋∣OP)的不足。在OOP中模塊化的關(guān)鍵單元是類(lèi)(classes),而在AOP中模塊化的單元?jiǎng)t是切面。切面能對(duì)關(guān)注點(diǎn)進(jìn)行模塊化,例如橫切多個(gè)類(lèi)型和對(duì)象的事務(wù)管理。(在AOP術(shù)語(yǔ)中通常稱(chēng)作橫切(crosscutting)關(guān)注點(diǎn)。)
AOP框架是Spring的一個(gè)重要組成部分。但是Spring IoC容器并不依賴(lài)于AOP,這意味著你有權(quán)利選擇是否使用AOP,AOP做為Spring IoC容器的一個(gè)補(bǔ)充,使它成為一個(gè)強(qiáng)大的中間件解決方案。
AOP在Spring Framework中的作用
提供聲明式企業(yè)服務(wù),特別是為了替代EJB聲明式服務(wù)。最重要的服務(wù)是聲明性事務(wù)管理。
允許用戶(hù)實(shí)現(xiàn)自定義切面,用AOP來(lái)完善OOP的使用。
如果你只打算使用通用的聲明式服務(wù)或者封裝好的聲明式中間件服務(wù),例如緩沖池(pooling),那么你不必與Spring AOP直接打交道,并且本章的大部分內(nèi)容可以跳過(guò)了。
首先讓我們從一些重要的AOP概念和術(shù)語(yǔ)開(kāi)始。這些術(shù)語(yǔ)不是Spring特有的。不過(guò)AOP術(shù)語(yǔ)并不是特別的直觀,如果Spring使用自己的術(shù)語(yǔ),將會(huì)變得更加令人困惑。
切面(Aspect):一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)可能會(huì)橫切多個(gè)對(duì)象。事務(wù)管理是J2EE應(yīng)用中一個(gè)關(guān)于橫切關(guān)注點(diǎn)的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式來(lái)實(shí)現(xiàn)。
連接點(diǎn)(Joinpoint):在程序執(zhí)行過(guò)程中某個(gè)特定的點(diǎn),比如某方法調(diào)用的時(shí)候或者處理異常的時(shí)候。在Spring AOP中,一個(gè)連接點(diǎn)總是表示一個(gè)方法的執(zhí)行。
通知(Advice):在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作。其中包括了“around”、“before”和“after”等不同類(lèi)型的通知(通知的類(lèi)型將在后面部分進(jìn)行討論)。許多AOP框架(包括Spring)都是以攔截器做通知模型,并維護(hù)一個(gè)以連接點(diǎn)為中心的攔截器鏈。
切入點(diǎn)(Pointcut):匹配連接點(diǎn)的斷言。通知和一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián),并在滿(mǎn)足這個(gè)切入點(diǎn)的連接點(diǎn)上運(yùn)行(例如,當(dāng)執(zhí)行某個(gè)特定名稱(chēng)的方法時(shí))。切入點(diǎn)表達(dá)式如何和連接點(diǎn)匹配是AOP的核心:Spring缺省使用AspectJ切入點(diǎn)語(yǔ)法。
引入(Introduction):用來(lái)給一個(gè)類(lèi)型聲明額外的方法或?qū)傩裕ㄒ脖环Q(chēng)為連接類(lèi)型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個(gè)對(duì)應(yīng)的實(shí)現(xiàn))到任何被代理的對(duì)象。例如,你可以使用引入來(lái)使一個(gè)bean實(shí)現(xiàn)IsModified
接口,以便簡(jiǎn)化緩存機(jī)制。
目標(biāo)對(duì)象(Target Object): 被一個(gè)或者多個(gè)切面所通知的對(duì)象。也被稱(chēng)做被通知(advised)對(duì)象。 既然Spring AOP是通過(guò)運(yùn)行時(shí)代理實(shí)現(xiàn)的,這個(gè)對(duì)象永遠(yuǎn)是一個(gè)被代理(proxied)對(duì)象。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對(duì)象,用來(lái)實(shí)現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中,AOP代理可以是JDK動(dòng)態(tài)代理或者CGLIB代理。
織入(Weaving):把切面連接到其它的應(yīng)用程序類(lèi)型或者對(duì)象上,并創(chuàng)建一個(gè)被通知的對(duì)象。這些可以在編譯時(shí)(例如使用AspectJ編譯器),類(lèi)加載時(shí)和運(yùn)行時(shí)完成。Spring和其他純Java AOP框架一樣,在運(yùn)行時(shí)完成織入。
通知類(lèi)型:
前置通知(Before advice):在某連接點(diǎn)之前執(zhí)行的通知,但這個(gè)通知不能阻止連接點(diǎn)之前的執(zhí)行流程(除非它拋出一個(gè)異常)。
后置通知(After returning advice):在某連接點(diǎn)正常完成后執(zhí)行的通知:例如,一個(gè)方法沒(méi)有拋出任何異常,正常返回。
異常通知(After throwing advice):在方法拋出異常退出時(shí)執(zhí)行的通知。
最終通知(After (finally) advice):當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)。
環(huán)繞通知(Around Advice):包圍一個(gè)連接點(diǎn)的通知,如方法調(diào)用。這是最強(qiáng)大的一種通知類(lèi)型。環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為。它也會(huì)選擇是否繼續(xù)執(zhí)行連接點(diǎn)或直接返回它自己的返回值或拋出異常來(lái)結(jié)束執(zhí)行。
環(huán)繞通知是最常用的通知類(lèi)型。和AspectJ一樣,Spring提供所有類(lèi)型的通知,我們推薦你使用盡可能簡(jiǎn)單的通知類(lèi)型來(lái)實(shí)現(xiàn)需要的功能。例如,如果你只是需要一個(gè)方法的返回值來(lái)更新緩存,最好使用后置通知而不是環(huán)繞通知,盡管環(huán)繞通知也能完成同樣的事情。用最合適的通知類(lèi)型可以使得編程模型變得簡(jiǎn)單,并且能夠避免很多潛在的錯(cuò)誤。比如,你不需要在JoinPoint
上調(diào)用用于環(huán)繞通知的proceed()
方法,就不會(huì)有調(diào)用的問(wèn)題。
在Spring 2.0中,所有的通知參數(shù)都是靜態(tài)類(lèi)型,因此你可以使用合適的類(lèi)型(例如一個(gè)方法執(zhí)行后的返回值類(lèi)型)作為通知的參數(shù)而不是使用Object
數(shù)組。
通過(guò)切入點(diǎn)匹配連接點(diǎn)的概念是AOP的關(guān)鍵,這使得AOP不同于其它僅僅提供攔截功能的舊技術(shù)。 切入點(diǎn)使得通知可以獨(dú)立對(duì)應(yīng)到面向?qū)ο蟮膶哟谓Y(jié)構(gòu)中。例如,一個(gè)提供聲明式事務(wù)管理 的環(huán)繞通知可以被應(yīng)用到一組橫跨多個(gè)對(duì)象的方法上(例如服務(wù)層的所有業(yè)務(wù)操作)。
Spring AOP使用純Java實(shí)現(xiàn)。它不需要專(zhuān)門(mén)的編譯過(guò)程。Spring AOP不需要控制類(lèi)裝載器層次,因此它適用于J2EE web容器或應(yīng)用服務(wù)器。
Spring目前僅支持使用方法調(diào)用作為連接點(diǎn)(join point)(在Spring bean上通知方法的執(zhí)行)。雖然可以在不影響到Spring AOP核心API的情況下加入對(duì)成員變量攔截器支持,但Spring并沒(méi)有實(shí)現(xiàn)成員變量攔截器。如果你需要把對(duì)成員變量的訪問(wèn)和更新也作為通知的連接點(diǎn),可以考慮其它的語(yǔ)言,如AspectJ。
Spring實(shí)現(xiàn)AOP的方法跟其他的框架不同。Spring并不是要提供最完整的AOP實(shí)現(xiàn)(盡管Spring AOP有這個(gè)能力),相反的,它其實(shí)側(cè)重于提供一種AOP實(shí)現(xiàn)和Spring IoC容器之間的整合,用于幫助解決在企業(yè)級(jí)開(kāi)發(fā)中的常見(jiàn)問(wèn)題。
因此,Spring的AOP功能通常都和Spring IoC容器一起使用。切面使用普通的bean定義語(yǔ)法來(lái)配置(盡管Spring提供了強(qiáng)大的"自動(dòng)代理(autoproxying)"功能):與其他AOP實(shí)現(xiàn)相比這是一個(gè)顯著的區(qū)別。有些事使用Spring AOP是無(wú)法輕松或者高效完成的,比如說(shuō)通知一個(gè)細(xì)粒度的對(duì)象(例如典型的域?qū)ο螅哼@種時(shí)候,使用AspectJ是最好的選擇。不過(guò)經(jīng)驗(yàn)告訴我們,對(duì)于大多數(shù)在J2EE應(yīng)用中適合用AOP來(lái)解決的問(wèn)題,Spring AOP都提供了一個(gè)非常好的解決方案。
Spring AOP從來(lái)沒(méi)有打算通過(guò)提供一種全面的AOP解決方案來(lái)與AspectJ競(jìng)爭(zhēng)。我們相信無(wú)論是基于代理(proxy-based)的框架如Spring AOP或者是成熟的框架如AspectJ都是很有價(jià)值的,他們之間應(yīng)該是互補(bǔ)而不是競(jìng)爭(zhēng)的關(guān)系。Spring 2.0可以無(wú)縫的整合Spring AOP,IoC和AspectJ,使得所有的AOP應(yīng)用完全融入基于Spring的應(yīng)用體系。這樣的集成不會(huì)影響Spring AOP API或者AOP Alliance API;Spring AOP保持了向下兼容性。下一章會(huì)詳細(xì)討論Spring AOP的API。
Spring Framework一個(gè)重要的原則就是無(wú)侵入性(non-invasiveness); 這個(gè)思想指你不應(yīng)當(dāng)被迫引入框架特定的類(lèi)和接口到你的業(yè)務(wù)/領(lǐng)域模型中。然而,Spring Framework在某些地方給你一個(gè)是否引入Spring框架特定依賴(lài)到你的代碼的選項(xiàng): 給你這個(gè)選項(xiàng)的理由是因?yàn)樵谔囟ǖ膱?chǎng)景中它可能僅僅是容易閱讀或用這種方法編寫(xiě)特定的功能塊。Spring Framework(幾乎)一直會(huì)為你提供這種選擇:從而使你能做出一個(gè)明智的決定,使它最適應(yīng)你的特定用例或場(chǎng)景。
你可以選擇AspectJ或者Spring AOP,以及選擇是使用@AspectJ注解風(fēng)格還是Spring XML配置風(fēng)格。事實(shí)上本章選擇先介紹@AspectJ風(fēng)格的方法不應(yīng)當(dāng)被看作是這樣一個(gè)暗示:Spring小組喜歡@AspectJ注解風(fēng)格更勝于Spring XML配置。
在第?6.4?節(jié) “AOP聲明風(fēng)格的選擇”一章有對(duì)使用各個(gè)風(fēng)格理由的一個(gè)更全面的討論。
Spring缺省使用J2SE 動(dòng)態(tài)代理(dynamic proxies)來(lái)作為AOP的代理。 這樣任何接口(或者接口集)都可以被代理。
Spring也可以使用CGLIB代理. 對(duì)于需要代理類(lèi)而不是代理接口的時(shí)候CGLIB代理是很有必要的。如果一個(gè)業(yè)務(wù)對(duì)象并沒(méi)有實(shí)現(xiàn)一個(gè)接口,默認(rèn)就會(huì)使用CGLIB。作為面向接口編程的最佳實(shí)踐,業(yè)務(wù)對(duì)象通常都會(huì)實(shí)現(xiàn)一個(gè)或多個(gè)接口。但也有可能會(huì)強(qiáng)制使用CGLIB,在這種情況(希望不常有)下,你可能需要通知一個(gè)沒(méi)有在接口中聲明的方法,或者需要傳入一個(gè)代理對(duì)象給方法作為具體類(lèi)型
為了明白Spring AOP是基于代理(proxy-based)的事實(shí),請(qǐng)參閱第?6.6.1?節(jié) “理解AOP代理”。