Why AOP?
AOP(Aspect-Oriented Programming),意思是面向切面編程。傳統(tǒng)的OOP面向?qū)ο笙喈?dāng)于站在一個(gè)上帝模式從上往下看,里面的一塊塊都是一個(gè)對(duì)象,由我任意組合;而AOP不同之處在于,他是以一個(gè)旁觀者的身法,從“側(cè)面”看整個(gè)系統(tǒng)模塊,看看哪里可以見縫插針,將自己想要處理的一段業(yè)務(wù)邏輯“編織”進(jìn)去。 Code duplication is the ultimate code smell. It’s a sign that something is very wrong with implementation or design.(重復(fù)的代碼會(huì)讓代碼的質(zhì)量很糟糕。如果出現(xiàn)這個(gè)狀況,那么一定是實(shí)現(xiàn)或者設(shè)計(jì)環(huán)境出了問題)
OOP本身是極力反對(duì)“重復(fù)發(fā)明輪子”的,但是有時(shí)卻對(duì)重復(fù)的代碼顯得無可奈何,而AOP本身是一種很好的能解決這個(gè)問題的一種思想。 抽象了半天,還是利用一個(gè)例子還更加形象的解釋吧。如果你要做一個(gè)權(quán)限系統(tǒng),那么肯定需要在很多業(yè)務(wù)邏輯之前都加上一個(gè)權(quán)限判斷——只有符合條件的才能完成后面的操作。如果利用傳統(tǒng)思想,很顯然你會(huì)把做權(quán)限判斷的業(yè)務(wù)邏輯做封裝,然后在每個(gè)業(yè)務(wù)邏輯執(zhí)行之前都執(zhí)行以下那片處理權(quán)限判斷的代碼。如下圖:
看到?jīng)],每次一個(gè)判斷每次一個(gè)判斷,如果讓這些權(quán)限判斷的代碼散落在系統(tǒng)的各個(gè)角落,那會(huì)是一個(gè)噩夢(mèng)!就算采用OOP思想,將權(quán)限檢查的業(yè)務(wù)放在一個(gè)類中,照樣無濟(jì)于事。因?yàn)槊慷螛I(yè)務(wù)代碼開頭總有這么一段抹不掉的身影(doSecurityCheck)。 這時(shí),AOP老兄終于按耐不住,要出場大展身手了!這位老兄馬上說,放著那段業(yè)務(wù)邏輯代碼,我來處理! 他首先將權(quán)限處理的部分視作一個(gè)aspect(切面),然后想辦法在運(yùn)行時(shí)把切面weave(編織)進(jìn)業(yè)務(wù)邏輯中合適的位置。比如就像這樣做: 這樣,AOP就成功的幫我把權(quán)限驗(yàn)證部分插入到調(diào)用代碼的前面執(zhí)行。具體調(diào)用哪個(gè)方法其實(shí)AOP并不知道,只要你把切面織入了用戶登錄,那后調(diào)用用戶登錄,只要你織入了用戶查詢,那就調(diào)用用戶查詢。而且不單單是只掉某一個(gè)方法,它可以挨著排的調(diào)用。 這只是其中一個(gè)強(qiáng)大的用處,還有像日志記錄、性能分析、事務(wù)處理等更多都可以利用到AOP的地方。 Think of AOP as complementing, not competing with, OOP. AOP can supplement OOP where it is weak. (AOP和OOP沒有競爭關(guān)系,相反,AOP能夠很好的補(bǔ)充OOP的不足) AOP基本術(shù)語
l Aspect(切面):就是你想給程序織入的代碼片段、如權(quán)限處理、日志記錄等。 l Weaving(編織):就是給指定的程序加上額外的業(yè)務(wù)邏輯的過程,比如將權(quán)限驗(yàn)證插入到用戶登錄的過程。 l Advice(通知):表示是在程序的哪里織入切面,比如前面織入,還是后面織入,或者是拋出異常的時(shí)候織入。 l Joinpoint(連接點(diǎn)):表示給那個(gè)程序織入切面,也就是被代理的目標(biāo)對(duì)象的目標(biāo)方法。 l Pointcut(切入點(diǎn)):表示給哪些程序織入切面,是連接點(diǎn)的集合,比如是用戶登錄和用戶查詢等都需要被織入。
為了方便用戶使用AOP,需要定義幾種通知類型。 l Before:前置通知,在業(yè)務(wù)邏輯之前通知 l After:后置通知,在業(yè)務(wù)邏輯正常完結(jié)之后通知 l End:結(jié)束通知,不管業(yè)務(wù)邏輯是否正常完結(jié),都會(huì)在后面執(zhí)行的通知 l Error:錯(cuò)誤通知,在業(yè)務(wù)邏輯拋出異常的時(shí)候通知
AOP執(zhí)行流程
下圖展示了AOP核心調(diào)用過程,通過調(diào)用AOP代理類,開始一個(gè)一個(gè)調(diào)用后面的(前置)通知/攔截器鏈條,完成之后在調(diào)用目標(biāo)方法,最后回來的時(shí)候接著調(diào)用(后置、結(jié)束)通知/攔截器鏈條。 如此一來就成功的完成了在AOP中給某個(gè)程序(目標(biāo)方法)之前加上一段業(yè)務(wù)邏輯,之后加上一段業(yè)務(wù)邏輯的流程,并且殺傷力極大,可以將目標(biāo)方法的范圍進(jìn)行任意控制。
動(dòng)手實(shí)現(xiàn)一個(gè)AOP
前戲那么長,高潮不會(huì)短!這次寫的AOP參考了很多Spring的代碼,吸收了大師補(bǔ)充的養(yǎng)分。 利用測試驅(qū)動(dòng)開發(fā)的原則,我們先來考慮考慮我們會(huì)怎么用(寫好測試代碼),然后想想API怎么設(shè)計(jì)(將接口寫好),最后考慮實(shí)現(xiàn)的問題。
參考spring的API設(shè)計(jì),我們給出了上面這段測試代碼。這樣就能給AOP代理工廠配置目標(biāo)對(duì)象,和各種各樣的通知,目前只有事務(wù)處理的通知。然后通過代理工廠獲取目標(biāo)對(duì)象的代理對(duì)象,并完成類型轉(zhuǎn)換的過程。最后調(diào)用指定方法,完成在這個(gè)方法的周圍實(shí)現(xiàn)事務(wù)處理過程。 設(shè)置目標(biāo)對(duì)象的方法無需贅言,就是看中了那個(gè)對(duì)象,設(shè)置進(jìn)去,這樣就能搞個(gè)代理幫他做了。。。 添加通知的實(shí)現(xiàn),我想設(shè)計(jì)的好用一點(diǎn)。什么叫好用呢?也就是給你一些接口,Before、After、Error、End等,只要你定義的aspect類(切面)實(shí)現(xiàn)了任意一個(gè)接口,就能保證按照這個(gè)接口名字所顯示的那樣執(zhí)行。比如我實(shí)現(xiàn)了Before接口和他的抽象方法,并在里面加了個(gè)“記錄日志”的功能,這樣,我以后就能在我的目標(biāo)方法執(zhí)行之前完成一次記錄日志的過程了。這里我們使用的是事務(wù)處理的aspect切面:
在代理工廠中,我們使用Object target存儲(chǔ)這個(gè)目標(biāo)對(duì)象,并使用集合來記錄所有該類涉及到的通知。
而在完成配置代理工廠后,需要通過這個(gè)代理工廠來獲取代理對(duì)象。在獲取代理對(duì)象之前,我們把之前完成的配置(目標(biāo)方法、通知集合)都初始化到AdvisedSupport對(duì)象中,將這個(gè)對(duì)象整體傳給后面的代理實(shí)現(xiàn)(jdk、cglib)完成代理類的初始化,以及通知和目標(biāo)方法的調(diào)用。
這里我們會(huì)根據(jù)不同的情況來判斷他是選擇jdk動(dòng)態(tài)代理還是選擇cglib動(dòng)態(tài)代理。 這里以jdk動(dòng)態(tài)代理為例,cglib留給讀者自行分析:
這里我們首先在getProxy初始化了代理類,然后當(dāng)代理類的方法被調(diào)用時(shí),會(huì)完成目標(biāo)方法調(diào)用,這個(gè)步驟都是ReflectiveMethodInvocation對(duì)象完成的。這個(gè)類實(shí)現(xiàn)了MethodInvocation,目的是為了完成之后的回調(diào)過程,這個(gè)后面可以看到。在這個(gè)ReflectiveMethodInvocation類里面,我們存儲(chǔ)了足夠多的信息
目的很簡單:就是將多個(gè)通知鏈條和目標(biāo)對(duì)象的方法本身的調(diào)用整合起來,形成邏輯完善的鏈條——前置通知在目標(biāo)方法前面排著隊(duì)完成,如果目標(biāo)方法拋出了異常就執(zhí)行錯(cuò)誤通知,一旦正常執(zhí)行完成目標(biāo)方法就執(zhí)行后置通知,而結(jié)束通知時(shí)不管是是正常執(zhí)行完目標(biāo)方法還是拋出了異常,最后都會(huì)執(zhí)行的一個(gè)通知。 下面來看看這個(gè)類中處理一連串方法調(diào)用的核心方法proceed():
很簡單,就是當(dāng)鏈條走完的時(shí)候,調(diào)用目標(biāo)方法。否則就繼續(xù)指向鏈條上的方法。這里有一個(gè)檢測是否匹配的過程,也就是我給我的切面類,也就是處理事務(wù)的切面TransactionAspect配置了一個(gè)注解Match,這個(gè)注解表示當(dāng)目標(biāo)類是org包下的某個(gè)類時(shí),我就會(huì)對(duì)他的doSomething方法完成攔截,在這個(gè)方法周圍加上事務(wù)處理。
具體判斷是否匹配,我采用的是正則表達(dá)式,也就是當(dāng)目標(biāo)類的某個(gè)方法被調(diào)用時(shí),一旦檢測到他符合methodMatch配置的正則表達(dá)式,就給該方法前后加上指定的邏輯。如果發(fā)現(xiàn)不匹配,這繼續(xù)尋找鏈條上下一個(gè)通知,直到走完整個(gè)通知鏈條。 這里大家肯定會(huì)有一個(gè)問題:在鏈條上獲取到一個(gè)通知,執(zhí)行該通知的時(shí)候,如何確保前置通知是再前面執(zhí)行,后置通知是再后面執(zhí)行呢?并且在完成調(diào)用之后確保執(zhí)行后面的通知調(diào)用流程? 其實(shí),spring在這里用到了一個(gè)很巧妙的編程技巧——通過多態(tài)原理和回調(diào)函數(shù)來處理。 這里獲取到了第index個(gè)通知,拿到的是個(gè)接口類型,但是實(shí)現(xiàn)類出賣了他的本質(zhì),表示它到底是前置還是后置或者是其他等。當(dāng)執(zhí)行invoke時(shí),將該MethodInvocation的實(shí)現(xiàn)類ReflectiveMethodInvocation的對(duì)象的引用傳遞進(jìn)去。如此調(diào)用的方法,其實(shí)是這樣的: 可以發(fā)現(xiàn),當(dāng)實(shí)現(xiàn)類是后置通知的時(shí)候,我會(huì)選擇AfterInterceptor來執(zhí)行,當(dāng)時(shí)前置通知的時(shí)候,會(huì)選擇BeforeInterceptor來執(zhí)行。也就是,碰到合適的通知,就采用合適的攔截器處理。 以前置通知的方法攔截器為例:
看到這里是不是有種似曾相識(shí)的感覺,這不就是開始那個(gè)權(quán)限驗(yàn)證的翻版么?對(duì)啊,it is。這里實(shí)現(xiàn)的很明確,就是在目標(biāo)方法調(diào)用之前執(zhí)行before中的業(yè)務(wù)邏輯,接著進(jìn)行mi.proceed()又回調(diào)到了我們MethodInvocation的實(shí)現(xiàn)類ReflectiveMethodInvocation中的proceed方法中了。 這樣,也就保證了鏈條的次序執(zhí)行。 來看看我們測試用例的輸出結(jié)果吧: 目前還沒能把ioc和aop整合起來使用,還有像ioc在web mvc框架中如何使用都還沒提到,不過這些會(huì)在我們以后的博客中不斷出現(xiàn)。 源代碼最后,放出源代碼:https://github.com/mjaow/my_spring |
|