1. 程式人生 > >什麼是面向切面程式設計AOP

什麼是面向切面程式設計AOP

昨天說了IoC,今天來說AOP。


IoC和AOP這兩個縮寫總是一起出現。在形式上,兩者同為三個字母的縮寫,而且第二個字母都是O,有對仗美;在性質上,兩者同為Spring的核心技術和特色,是最常被提起的概念。


但與面向切面程式設計AOP真正對應的,是OOP,即面向物件程式設計。

未說面向切面,先說面向過程。


面向物件側重靜態,名詞,狀態,組織,資料,載體是空間;

面向過程側重動態,動詞,行為,呼叫,演算法,載體是時間;


這兩者,運行於不同維度,本不互相沖突,理應攜手合作,相互配合。


所以,web專案中的controller,service,dao等各層元件,有行為無狀態,有方法無屬性,即使有屬性,也只是對下一層元件的持有;

所以,web專案中的entity,dto等各種實體,有狀態無行為,有屬性無方法,即使有方法,也只是getter/setter等,圍著狀態打轉;


反倒是我們剛學「面向物件」時說的「既有眼睛又會叫」的小狗那種狀態行為俱全的物件,基本見不到了。


程式需要狀態,但物件不需要狀態。

如果物件有了狀態,就會引發煩人的多執行緒問題,在叢集環境下更是麻煩。

程式的狀態,統一由資料庫,快取,任務佇列這些外部容器來容納,在處理時,僅僅在物件的方法中以區域性變數的面目偶爾出現,被封線上程內部,朝生夕滅,任由回收。


基於Java語言的web開發,本質是用面向物件的組織,面向過程的邏輯,來解決問題。應用實踐中靈活具體,不拘泥,不教條。


但仍會遇到一種麻煩,即假如一個流程分三個步驟,分別是X,A,Y,另一個流程的三個步驟是X,B,Y。

寫在程式裡,兩個方法體分別是XAY和XBY,顯然,這出現了重複,違反了DRY原則。

你可以把X和Y分別抽成一個方法,但至少還是要寫一條語句來呼叫方法,xAy,xBy,重複依然存在。


如果控制反轉來處理這問題,將採用模板方法的模式,在抽象父類方法體中宣告x?y,其中?部分為抽象方法,由具體子類實現。

但這就出現了繼承,而且呼叫者只能呼叫父類宣告的方法,耦合性太強,不靈活。

所以,我們常看到,只有那些本來就是呼叫者呼叫父類宣告的方法的情況,比如表現層,或者本來就不用太靈活,比如只提供增刪改查的持久層,才總出現抽象父類的身影。


具體Controller is-a 抽象Controller,具體Dao is-a 抽象Dao,這大家都能接受。

但除了在抽象Controller、抽象Dao中固定的步驟之外,我們就不需要點別的嗎?

比如在某些Controller方法執行之前做點什麼,在某些Dao方法執行之前之後做點什麼?

而且最好能基於配置,基於約定,而不是都死乎乎硬編碼到程式碼裡。


這些需求,基本的程式設計手段就解決不了了。

於是乎,面向切面橫空出世。

《Spring3.x企業應用開發實戰》(下稱《3.x》)第6章寫道:

AOP是OOP的有益補充。

Spring實現的AOP是代理模式,給呼叫者使用的實際是已經過加工的物件,你程式設計時方法體裡只寫了A,但呼叫者拿到的物件的方法體卻是xAy。


x和y總還是需要你來寫的,這就是增強。

x和y具體在什麼時候被呼叫總還是需要你來規定的,雖然是基於約定的宣告這種簡單的規定,這就是切點。

《EXPERT ONE ON ONE J2EE DEVELOPMENT WITHOUT EJB》第8章、《Spring實戰》第4章:

增強(advice,另譯為通知,但《3.x》作者不贊成):在特定連線點執行的動作。

切點(pointcut):一組連線點的總稱,用於指定某個增強應該在何時被呼叫。

連線點(join point):在應用執行過程中能夠插入切面的一個點。(我注:就是抽象的「切點」宣告所指代的那些具體的點。)

切面(aspect):通知(即增強)和切點的結合。

其他概念不贅,如果有興趣可以自行去翻書,我每次看到這些東西都很頭大。


用人話說就是,增強是「幹啥」,切入點是「啥時候幹」。

生活中例子如端碗-吃飯-放筷子,端碗-吃麵-放筷子,你只要定義好端碗和放筷子,並宣告在吃點啥之前之後呼叫它們,業務方法只要實現吃飯、吃麵就行了,以後想加個吃餃子也很方便。

生產中例子如事務、安全、日誌(*),用宣告的方式一次性配好,之後漫漫長夜專注於寫業務程式碼就行了,不再為這些事而煩。

《Spring實戰》第4章:

散佈於應用中多處的功能(日誌、安全、事務管理等)被稱為橫切關注點。

把橫切關注點與業務邏輯分離是AOP要解決的問題。

*:但《Spring3.x企業應用開發實戰》第6章說:

很多人認為很難用AOP編寫實用的程式日誌。筆者對此觀點非常認同。(我注:我也認同)


總之,面向切面的目標與面向物件的目標沒有不同。

一是減少重複,二是專注業務。

相比之下,面向物件是細膩的,用繼承和組合的方式,綿綿編織成一套類和物件體系。

而面向切面是豪放的,大手一揮:凡某包某類某開頭的方法,一併如斯處理!


《Javascript DOM程式設計藝術》說,dom是繡花針,innerHTML是砍柴斧。

我看面向物件和麵向切面,也可做如是觀。


沒有依賴注入,面向切面就失去立足之本。

沒有面向切面,依賴注入之後也只好在各個方法裡下死力氣寫重複程式碼,或者搞出來一個超級複雜的抽象基類。

同時有了這兩者,才真正能履行拆分、解耦、模組化、約定優於配置的思想,才真正能實現合併重複程式碼、專注業務邏輯的願望。


不過,這面向切面不是Spring的專利,Java Web開發中最基本的Filter,就是一層一層的切面,突破了之後才能觸及Servlet這核心。

但Filter過於暴力粗放,只能執行在Servlet之外而不能在之內,能上不能下,稍微細一點的批處理它就不行了,而Spring的AOP可以。

(Struts2的Intercepter也算,關於這就不多說了,如感興趣可看《Struts2技術內幕》第8章Intercepter部分)

從理論上說,Filter和Spring AOP前者是責任鏈模式(Struts2 Intercepter也是),後者是代理模式,性質不同,但從「層層包裹核心」的共同特點看,是一致的。


所以無論是寬是窄,只要你遇到了「好多方法裡都有重複程式碼好臭哇呀」的情況(關於程式碼的壞氣味可以參考《重構》),而又無法應用策略、裝飾器、模板方法等模式,就考慮AOP吧!

畢竟雖然Spring的書籍裡講到AOP就連篇累牘、名詞繁多、配法多樣、望而生畏,但具體寫起來還是非常簡單的。

(不過,如果能用「繡花針」OOP的設計模式實現,還是不建議輕易動用AOP這「劈柴刀」,不得已才用之。關於設計模式,推薦《Java與模式》一書)