AOP實現原理

【轉自】http://blog.csdn.net/kaz33/article/details/4545753

       面向方面編程(Aspect Oriented Programming,簡稱AOP)是一種聲明式編程(Declarative Programming)。聲明式編程是和命令式編程(Imperative Programming)相對的概念。我們平時使用的編程語言,比如C++、Java、Ruby、Python等,都屬命令式編程。命令式編程的意思是,程序員需要一步步寫清楚程序需要如何做什麼(How to do What)。聲明式編程的意思是,程序員不需要一步步告訴程序如何做,只需要告訴程序在哪些地方做什麼(Where to do What)。比起命令式編程來,聲明式編程是在一個更高的層次上編程。聲明式編程語言是更高級的語言。聲明式編程通常處理一些總結性、總覽性的工作,不適合做順序相關的細節相關的底層工作。

       如果說命令式編程是拼殺在第一線的基層工作人員,聲明式編程就是總設計師、規則制定者。聲明式編程語言的概念,和領域專用語言(Domain Specific Language,簡稱DSL)的概念有相通之處。DSL主要是指一些對應專門領域的高層編程語言,和通用編程語言的概念相對。DSL對應的專門領域(Domain)一般比較狹窄,或者對應於某個行業,或者對應於某一類具體應用程序,比如數據庫等。

       最常見的DSL就是關係數據庫的結構化數據查詢語言SQL。同時,SQL也是一門聲明式語言。SQL只需要告訴數據庫,處理符合一定條件的數據,而不需要自己一步步判斷每一條數據是否符合條件。SQL的形式一般是 select … where …,update … where …,delete … where …。當然,這樣一來,很多基層工作,SQL做不了。因此,大部分數據庫都提供了另外的命令式編程語言,用來編寫存儲過程等,以便處理一些更加細節的工作。

       常見的DSL還有規則引擎(Rule Engine)語言、工作流(Workflow)語言等。規則引擎和工作流同時帶有命令式編程和聲明式編程的特點。規則引擎允許用戶按照優先級定義一系列條件組合,並定義對滿足條件的數據的處理過程。工作流也大致類似。工作流把最基本的條件判斷和循環語句的常見組合,定義爲更加高級複雜的常用程序流程邏輯塊。用戶可以用這些高級流程塊組合更加複雜的流程塊,從而定義更加複雜的流程跳轉條件。用戶也可以定義當程序運行上下文滿足一定條件的時候,應該做什麼樣的處理工作。規則引擎和工作流的語言形式有可能是XML格式,也有可能是Ruby、Python、Javascript等腳本格式。我個人比較傾向於腳本格式,因爲XML適合表達結構化數據,而不擅長表達邏輯流程。當然,XML格式的好處也是顯而易見的。解析器可以很容易分析XML文件的結構,XML定義的條件或者程序流程都可以很方便地作爲數據來處理。

      介紹了聲明式編程和DSL之後,我們來看本章題目表達的內容——AOP。AOP是聲明式編程,AOP語言也可以看作是DSL。AOP語言對應的專門領域(Domain)就是程序結構的方方面面(Aspect),比如程序的類、方法、成員變量等結構,以及針對這些程序結構的通用工作處理,比如日誌管理、權限管理、事務管理等。

       AOP處理的工作內容一般都是這樣的一些總結性工作:“我想讓所有的數據庫類都自動進行數據庫映射”、“我想打印出所有業務類的工作流程日誌”、“我想給所有關鍵業務方法都加上事務管理功能”、“我想給所有敏感數據處理方法都加上安全管理授權機制”等等。 
下面我們介紹AOP的實現原理和使用方法。

AOP實現原理

AOP的實現原理可以看作是Proxy/Decorator設計模式的泛化。我們先來看Proxy模式的簡單例子。

  1. Proxy { 
  2.     innerObject; // 真正的對象 
  3.     f1() { 
  4.         // 做一些額外的事情 
  5.  
  6.         innerObject.f1(); // 調用真正的對象的對應方法 
  7.  
  8.           // 做一些額外的事情 
  9.     } 

       在Python、Ruby等動態類型語言中,只要實現了f1()方法的類,都可以被Proxy包裝。在Java等靜態類型語言中,則要求Proxy和被包裝對象實現相同的接口。動態語言實現Proxy模式要比靜態語言容易得多,動態語言實現AOP也要比靜態語言容易得多。假設我們用Proxy包裝了10個類,我們通過調用Proxy的f1()方法來調用這10個類的f1()方法,這樣,所有的f1()調用都會執行同樣的一段“額外的工作”,從而實現了“所有被Proxy包裝的類,都執行一段同樣的額外工作”的任務。這段“額外的工作”可能是進行日誌記錄,權限檢查,事務管理等常見工作。

       Proxy模式是可以疊加的。我們可以定義多種完成特定方面任務(Aspect),比如,我們可以定義LogProxy、SecurityProxy、TransactionProxy,分別進行日誌管理、權限管理、事務管理。

  1. LogProxy { 
  2.       f1(){ 
  3.             // 記錄方法進入信息 
  4.  
  5.             innerObject.f1();// 調用真正的對象的對應方法 
  6.  
  7.           // 記錄方法退出信息 
  8.     } 
  9.  
  10. SecurityProxy { 
  11.       f1(){ 
  12.           // 進行權限驗證 
  13.  
  14.           innerObject.f1();// 調用真正的對象的對應方法 
  15.       } 
  16.  
  17. TransactonProxy { 
  18.       f1(){ 
  19.           Open Transaction 
  20.  
  21.           innerObject.f1();// 調用真正的對象的對應方法 
  22.  
  23.           Close Transaction 
  24.       } 

       根據AOP的慣用叫法,上述的這些Proxy也叫做Advice。這些Proxy(or Advice)可以按照一定的內外順序套起來,最外面的Proxy會最先執行。包裝f1()方法,也叫做截獲(Intercept)f1()方法。Proxy/Advice有時候也叫做Interceptor。

       看到這裏,讀者可能會產生兩個問題。

       問題一:上述代碼採用的Proxy模式只是面向對象的特性,怎麼會扯上一個新概念“面向方面(AOP)”呢?

       問題二:Proxy模式雖然避免了重複“額外工作”代碼的問題,但是,每個相關類都要被Proxy包裝,這個工作也是很煩人。AOP Proxy如何能在應用程序中大規模使用呢?

       下面我們來解答着兩個問題。

       對於問題一,我們來看一個複雜一點的例子。假設被包裝對象有f1()和f2()兩個方法都要被包裝。

  1. RealObject{ 
  2.       f1() {…} 
  3.       f2() {…} 
  4. }

       這個時候,我們應該如何做?難道讓Proxy也定義f1()和f2()兩個方法?就象下面代碼這樣?

  1. Proxy { 
  2.     innerObject; // 真正的對象 
  3.     f1() { 
  4.         // 做一些額外的事情 
  5.  
  6.         innerObject.f1(); // 調用真正的對象的對應方法 
  7.  
  8.         // 做一些額外的事情 
  9.     } 
  10.  
  11.     f2() { 
  12.         // 做一些額外的事情 
  13.         
  14.         innerObject.f2(); // 調用真正的對象的對應方法 
  15.         
  16.         // 做一些額外的事情 
  17.     } 
  18.  

       這樣做有幾個不利之處。一是會造成代碼重複,Proxy的f1()和f2()裏面的“做一些額外的事情”代碼重複。二是難以擴展,被包裝對象可能有多個不同的方法,不同的被包裝對象需要被包裝的方法也可能不同。現在的問題就變成,“Proxy如何才能包裝截獲任何類的任何方法?” 
答案呼之欲出。對,就是Reflection。Java、Python、Ruby都支持Reflection,都支持Method(方法)對象。那麼我們就利用Method Reflection編寫一個能夠解惑任何類的任何方法的Proxy/Advice/Interceptor。

  1. MethodInterceptor{ 
  2.  
  3.     around( method ){ 
  4.         // 做些額外的工作 
  5.         
  6.         method.invoke(…); // 調用真正的對象方法 
  7.         
  8.         // 做些額外的工作 
  9.     } 

       上述的MethodInterceptor就可以分別包裝和截獲f1()和f2()兩個方法。

       這裏的method參數就是方法對象,在Java、Ruby等面嚮對象語言中,需要用Reflection獲取方法對象。這個方法對象就相當於函數式編程的函數對象。在函數式編程中,函數對象屬於“一等公民”,函數對象的獲取不需要經過Reflection機制。所以,函數式編程對AOP的支持,比面向對象編程更好。由此我們看到,AOP對應的問題領域確實超出了OOP的力所能及的範圍。OOP只能處理同一個類體系內的同一個方法簽名的截獲和包裝工作,一旦涉及到一個類的多個不同方法,或者多個不同類體系的不同方法,OOP就黔驢技窮,無能爲力了。

      使用Method Reflection的方式截獲任何方法對象,是AOP的常用實現手段之一。另一個常見手段就是自動代碼生成了。這也回答了前面提出的問題二——如何在應用系統中大規模使用AOP。

        Proxy Pattern + Method Reflection + 自動代碼生成這樣一個三元組合,就是AOP的基本實現原理。Proxy Pattern 和 Method Reflection,前面已經做了闡述,下面我們來講解自動代碼生成。

首先,AOP需要定義一種Aspect描述的DSL。Aspect DSL主要用來描述這樣的內容:“用TransactionProxy包裝截獲business目錄下的所有類的公共業務方法”、“ 用SecurityProxy包裝截獲所有Login/Logout開頭的類的所有公共方法”、“用LogProxy包裝截獲所有文件的所有方法”等等。Aspect DSL的形式有多種多樣。有的是一種類似Java的語法,比如AspectJ;有的是XML格式或者各種腳本語言,比如,Spring AOP等。

有了Aspect DSL,AOP處理程序就可以生成代碼了。AOP生成代碼有三種可能方式:

(1)靜態編譯時期,源代碼生成。爲每個符合條件的類方法產生對應的Proxy對象。AspectJ以前就是這種方式。

(2)靜態編譯時期,處理編譯後的字節碼。Java、Python之類的虛擬機語言都有一種中間代碼(Java的中間代碼叫做字節碼),AOP處理程序可以分析字節碼,並直接產生字節碼形式的Proxy。這種方式也叫做靜態字節碼增強。AspectJ也支持這種方式。Java有一些開源項目,比如 ASM、Cglib等,可以分析並生成Java字節碼。這些開源項目不僅可以靜態分析增強字節碼,還可以在程序運行期動態分析增強字節碼。很多AOP項目,比如Spring AOP,都採用ASM/Cglib處理字節碼。

(3)動態運行時期,即時處理裝載到虛擬機內部的類結構字節碼。這也叫做動態增強。比如,Spring AOP。如前所述,Spring AOP使用ASM/Cglib之類的處理字節碼的開源項目。Java運行庫本身也提供了類似於ASM/Cglib的簡單的動態處理字節碼的API,叫做 Dynamic Proxy。

以上就是AOP的實現原理:Proxy Pattern + Method Reflection + Aspect DSL + 自動代碼生成

總體來說,實現AOP的便利程度,函數式編程語言 > 動態類型語言 > 靜態類型語言。當然,這個不等式並不是絕對的。有些動態類型語言提供了豐富強大的語法特性,實現AOP的便利程度,可能要超過函數式編程語言。


發佈了26 篇原創文章 · 獲贊 9 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章