AOP知識(shí)整理
AOP(Aspect-Oriented Programming):面向切面的編程成肘。OOP(Object-Oriented Programming)面向?qū)ο蟮木幊檀懒铡?duì)于OOP我們已經(jīng)再熟悉不過了,對(duì)于AOP沦疾,可能我們會(huì)覺得是一種新特性侵蒙,其實(shí)AOP是對(duì)OOP的一種補(bǔ)充造虎,OOP面向的是縱向編程,繼承纷闺、封裝算凿、多態(tài)是其三大特性份蝴,而AOP是面向橫向的編程。
面向切面編程(AOP)通過提供另外一種思考程序結(jié)構(gòu)的途經(jīng)來彌補(bǔ)面向?qū)ο缶幊蹋∣OP)的不足澎媒。在OOP中模塊化的關(guān)鍵單元是類(classes)搞乏,而在AOP中模塊化的單元?jiǎng)t是切面。切面能對(duì)關(guān)注點(diǎn)進(jìn)行模塊化戒努,例如橫切多個(gè)類型和對(duì)象的事務(wù)管理请敦。
AOP框架是Spring的一個(gè)重要組成部分。但是Spring IoC容器并不依賴于AOP储玫,這意味著你有權(quán)利選擇是否使用AOP侍筛,AOP做為Spring IoC容器的一個(gè)補(bǔ)充,使它成為一個(gè)強(qiáng)大的中間件解決方案。
AOP在Spring Framework中的作用
提供聲明式企業(yè)服務(wù)撒穷,特別是為了替代EJB聲明式服務(wù)匣椰。最重要的服務(wù)是聲明性事務(wù)管理(這個(gè)我想是AOP使用最多的一處了)。
允許用戶實(shí)現(xiàn)自定義切面端礼,用AOP來完善OOP的使用禽笑。
1. AOP概念:
學(xué)習(xí)AOP,當(dāng)然得先了解一下其眾多的概念性術(shù)語:
切面(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注解的方式來實(shí)現(xiàn)蟀伸。
連接點(diǎn)(Joinpoint):在程序執(zhí)行過程中某個(gè)特定的點(diǎn),比如某方法調(diào)用的時(shí)候或者處理異常的時(shí)候缅刽。在Spring AOP中啊掏,一個(gè)連接點(diǎn)總是表示一個(gè)方法的執(zhí)行。
通知(Advice):在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作衰猛。其中包括了“around”迟蜜、“before”和“after”等不同類型的通知(通知的類型將在后面部分進(jìn)行討論)。許多AOP框架(包括Spring)都是以攔截器做通知模型啡省,并維護(hù)一個(gè)以連接點(diǎn)為中心的攔截器鏈娜睛。
切入點(diǎn)(Pointcut):匹配連接點(diǎn)的斷言。通知和一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián)冕杠,并在滿足這個(gè)切入點(diǎn)的連接點(diǎn)上運(yùn)行(例如,當(dāng)執(zhí)行某個(gè)特定名稱的方法時(shí))酸茴。切入點(diǎn)表達(dá)式如何和連接點(diǎn)匹配是AOP的核心:Spring缺省使用AspectJ切入點(diǎn)語法分预。
引入(Introduction):用來給一個(gè)類型聲明額外的方法或?qū)傩裕ㄒ脖环Q為連接類型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個(gè)對(duì)應(yīng)的實(shí)現(xiàn))到任何被代理的對(duì)象薪捍。例如笼痹,你可以使用引入來使一個(gè)bean實(shí)現(xiàn)IsModified接口配喳,以便簡(jiǎn)化緩存機(jī)制。
目標(biāo)對(duì)象(Target Object): 被一個(gè)或者多個(gè)切面所通知的對(duì)象凳干。也被稱做被通知(advised)對(duì)象晴裹。 既然Spring AOP是通過運(yùn)行時(shí)代理實(shí)現(xiàn)的,這個(gè)對(duì)象永遠(yuǎn)是一個(gè)被代理(proxied)對(duì)象救赐。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對(duì)象涧团,用來實(shí)現(xiàn)切面契約(例如通知方法執(zhí)行等等)。在Spring中经磅,AOP代理可以是JDK動(dòng)態(tài)代理或者CGLIB代理泌绣。
織入(Weaving):把切面連接到其它的應(yīng)用程序類型或者對(duì)象上,并創(chuàng)建一個(gè)被通知的對(duì)象预厌。這些可以在編譯時(shí)(例如使用AspectJ編譯器)阿迈,類加載時(shí)和運(yùn)行時(shí)完成。Spring和其他純Java AOP框架一樣轧叽,在運(yùn)行時(shí)完成織入苗沧。
通知類型:
前置通知(Before advice):在某連接點(diǎn)之前執(zhí)行的通知,但這個(gè)通知不能阻止連接點(diǎn)之前的執(zhí)行流程(除非它拋出一個(gè)異常)炭晒。
后置通知(After returning advice):在某連接點(diǎn)正常完成后執(zhí)行的通知:例如待逞,一個(gè)方法沒有拋出任何異常,正常返回腰埂。
異常通知(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)大的一種通知類型。環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為驴一。它也會(huì)選擇是否繼續(xù)執(zhí)行連接點(diǎn)或直接返回它自己的返回值或拋出異常來結(jié)束執(zhí)行休雌。
環(huán)繞通知是最常用的通知類型。和AspectJ一樣肝断,Spring提供所有類型的通知杈曲,我們推薦你使用盡可能簡(jiǎn)單的通知類型來實(shí)現(xiàn)需要的功能。例如胸懈,如果你只是需要一個(gè)方法的返回值來更新緩存担扑,最好使用后置通知而不是環(huán)繞通知,盡管環(huán)繞通知也能完成同樣的事情趣钱。用最合適的通知類型可以使得編程模型變得簡(jiǎn)單涌献,并且能夠避免很多潛在的錯(cuò)誤。比如首有,你不需要在JoinPoint上調(diào)用用于環(huán)繞通知的proceed()方法燕垃,就不會(huì)有調(diào)用的問題枢劝。
在這里,基于@AspectJ的AOP我就不多寫了卜壕,因?yàn)槲腋嗖A于Spring中使用ProxyFactoryBean創(chuàng)建AOP代理您旁。
2.使用ProxyFactoryBean創(chuàng)建AOP代理:
在Spring里創(chuàng)建一個(gè)AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。 這個(gè)類對(duì)應(yīng)用的切入點(diǎn)和通知提供了完整的控制能力(包括它們的應(yīng)用順序)轴捎。像其它的FactoryBean實(shí)現(xiàn)一樣鹤盒,ProxyFactoryBean引入了一個(gè)間接層。如果你定義一個(gè)名為foo的ProxyFactoryBean轮蜕, 引用foo的對(duì)象看到的將不是ProxyFactoryBean實(shí)例本身昨悼,而是一個(gè)ProxyFactoryBean實(shí)現(xiàn)里getObject() 方法所創(chuàng)建的對(duì)象。 這個(gè)方法將創(chuàng)建一個(gè)AOP代理跃洛,它包裝了一個(gè)目標(biāo)對(duì)象率触。
ProxyFactoryBean類本身也是一個(gè)JavaBean,其屬性主要有如下用途:
指定你希望代理的目標(biāo)對(duì)象
指定是否使用CGLIB汇竭。
一些主要屬性從org.springframework.aop.framework.ProxyConfig里繼承下來(這個(gè)類是Spring里所有AOP代理工廠的父類)葱蝗。這些主要屬性包括:
proxyTargetClass:這個(gè)屬性為true時(shí),目標(biāo)類本身被代理而不是目標(biāo)類的接口细燎。如果這個(gè)屬性值被設(shè)為true两曼,CGLIB代理將被創(chuàng)建。
optimize:用來控制通過CGLIB創(chuàng)建的代理是否使用激進(jìn)的優(yōu)化策略玻驻。 除非完全了解AOP代理如何處理優(yōu)化悼凑,否則不推薦用戶使用這個(gè)設(shè)置。目前這個(gè)屬性僅用于CGLIB代理璧瞬; 對(duì)于JDK動(dòng)態(tài)代理(缺省代理)無效户辫。
frozen:如果一個(gè)代理配置是frozen的,就不允許對(duì)該配置進(jìn)行修改嗤锉。 這在簡(jiǎn)單優(yōu)化和不希望調(diào)用者在代理創(chuàng)建后操作代理(通過Advised接口) 時(shí)很有用渔欢。缺省值為false,即可以進(jìn)行類似添加附加通知的操作瘟忱。
exposeProxy:決定當(dāng)前代理是否被暴露在一個(gè)ThreadLocal 中以便被目標(biāo)對(duì)象訪問奥额。如果目標(biāo)對(duì)象需要獲取代理而且exposeProxy屬性被設(shè)為 true,目標(biāo)對(duì)象可以使用AopContext.currentProxy()方法访诱。
aopProxyFactory:使用AopProxyFactory的實(shí)現(xiàn)垫挨。這提供了一種方法來自定義是否使用動(dòng)態(tài)代理,CGLIB或其它代理策略触菜。 缺省實(shí)現(xiàn)將根據(jù)情況選擇動(dòng)態(tài)代理或者CGLIB九榔。一般情況下應(yīng)該沒有使用這個(gè)屬性的需要;它是被設(shè)計(jì)來在Spring 1.1中添加新的代理類型的。
ProxyFactoryBean中需要說明的其它屬性包括:
-
proxyInterfaces:需要代理的接口名的字符串?dāng)?shù)組帚屉。 如果沒有提供,將為目標(biāo)類使用一個(gè)CGLIB代理漾峡。
-
interceptorNames:Advisor的字符串?dāng)?shù)組攻旦,可以包括攔截器或其它通知的名字。 順序是很重要的生逸,排在前面的將被優(yōu)先服務(wù)牢屋。就是說列表里的第一個(gè)攔截器將能夠第一個(gè)攔截調(diào)用。這里的名字是當(dāng)前工廠中bean的名字槽袄,包括父工廠中bean的名字烙无。這里你不能使用bean的引用因?yàn)檫@會(huì)導(dǎo)致ProxyFactoryBean忽略通知的單例設(shè)置。你可以把一個(gè)攔截器的名字加上一個(gè)星號(hào)作為后綴(*)遍尺。這將導(dǎo)致這個(gè)應(yīng)用程序里所有名字以星號(hào)之前部分開頭的通知器都被應(yīng)用截酷。
單例:工廠是否應(yīng)該返回同一個(gè)對(duì)象,不論方法getObject()被調(diào)用的多頻繁乾戏。 多個(gè)FactoryBean實(shí)現(xiàn)都提供了這個(gè)方法迂苛。缺省值是true。 如果你希望使用有狀態(tài)的通知–例如鼓择,有狀態(tài)的mixin–可以把單例屬性的值設(shè)置為false來使用原型通知三幻。
3.基于JDK和CGLIB的代理:
如果一個(gè)需要被代理的目標(biāo)對(duì)象的類(后面將簡(jiǎn)單地稱它為目標(biāo)類)沒有實(shí)現(xiàn)任何接口,那么一個(gè)基于CGLIB的代理將被創(chuàng)建呐能。 這是最簡(jiǎn)單的場(chǎng)景念搬,因?yàn)镴DK代理是基于接口的,沒有接口意味著沒有使用JDK進(jìn)行代理的可能.
如果ProxyFactoryBean的proxyTargetClass屬性被設(shè)為true摆出,那么一個(gè)基于CGLIB的代理將創(chuàng)建朗徊。 這樣的規(guī)定是有意義的,遵循了最小驚訝法則(保證了設(shè)定的一致性)懊蒸。甚至當(dāng)ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名荣倾, 而proxyTargetClass屬性被設(shè)置為true仍然將實(shí)際使用基于CGLIB的代理。
如果ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名骑丸,一個(gè)基于JDK的代理將被創(chuàng)建舌仍。 被創(chuàng)建的代理將實(shí)現(xiàn)所有在proxyInterfaces屬性里被說明的接口; 如果目標(biāo)類實(shí)現(xiàn)了全部在proxyInterfaces屬性里說明的接口以及一些額外接口通危,返回的代理將只實(shí)現(xiàn)說明的接口而不會(huì)實(shí)現(xiàn)那些額外接口铸豁。
如果ProxyFactoryBean的proxyInterfaces屬性沒有被設(shè)置, 但是目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者更多)接口菊碟,那么ProxyFactoryBean將自動(dòng)檢測(cè)到這個(gè)目標(biāo)類已經(jīng)實(shí)現(xiàn)了至少一個(gè)接口节芥, 一個(gè)基于JDK的代理將被創(chuàng)建。被實(shí)際代理的接口將是目標(biāo)類所實(shí)現(xiàn)的全部接口; 實(shí)際上头镊,這和在proxyInterfaces屬性中列出目標(biāo)類實(shí)現(xiàn)的每個(gè)接口的情況是一樣的蚣驼。 然而,這將顯著地減少工作量以及輸入錯(cuò)誤的可能性