Spring之AOP原理_動態代理

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

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

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

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

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

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

AOP實現原理編程語言

AOP的實現原理能夠看做是Proxy/Decorator設計模式的泛化。咱們先來看Proxy模式的簡單例子。函數式編程

 Proxy { 
     innerObject; // 真正的對象 
     f1() { 
         // 作一些額外的事情 
  
        innerObject.f1(); // 調用真正的對象的對應方法 
 
           // 作一些額外的事情 
     } 
}


PythonRuby等動態類型語言中,只要實現了f1()方法的類,均可以被Proxy包裝。在Java等靜態類型語言中,則要求Proxy和被包裝對象實現相同的接口。動態語言實現Proxy模式要比靜態語言容易得多,動態語言實現AOP也要比靜態語言容易得多。假設咱們用Proxy包裝了10個類,咱們經過調用Proxyf1()方法來調用這10個類的f1()方法,這樣,全部的f1()調用都會執行一樣的一段「額外的工做」,從而實現了「全部被Proxy包裝的類,都執行一段一樣的額外工做」的任務。這段「額外的工做」多是進行日誌記錄,權限檢查,事務管理等常見工做。函數

Proxy模式是能夠疊加的。咱們能夠定義多種完成特定方面任務(Aspect),好比,咱們能夠定義LogProxySecurityProxyTransactionProxy,分別進行日誌管理、權限管理、事務管理。spa

 LogProxy { 
      f1(){ 
           // 記錄方法進入信息 
  
            innerObject.f1();// 調用真正的對象的對應方法 
          // 記錄方法退出信息 
   } 
} 
 SecurityProxy { 
      f1(){ 
          // 進行權限驗證 
 
          innerObject.f1();// 調用真正的對象的對應方法 
       } 
} 
TransactonProxy { 
      f1(){ 
          Open Transaction 
          innerObject.f1();// 調用真正的對象的對應方法 
         Close Transaction 
      } 
}


根據AOP的慣用叫法,上述的這些Proxy也叫作Advice。這些Proxyor Advice)能夠按照必定的內外順序套起來,最外面的Proxy會最早執行。包裝f1()方法,也叫作截獲(Interceptf1()方法。Proxy/Advice有時候也叫作Interceptor

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

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

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

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

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

RealObject{ 
      f1() {…} 
      f2() {…} 
 }


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

 Proxy { 
    innerObject; // 真正的對象 
    f1() { 
        // 作一些額外的事情 
 
       innerObject.f1(); // 調用真正的對象的對應方法 

        // 作一些額外的事情 
     }   
    f2() { 
       // 作一些額外的事情 
       
        innerObject.f2(); // 調用真正的對象的對應方法 
       
        // 作一些額外的事情 
  }  
}


這樣作有幾個不利之處。一是會形成代碼重複,Proxyf1()f2()裏面的「作一些額外的事情」代碼重複。二是難以擴展,被包裝對象可能有多個不一樣的方法,不一樣的被包裝對象須要被包裝的方法也可能不一樣。如今的問題就變成,「Proxy如何才能包裝截獲任何類的任何方法?」 
答案呼之欲出。對,就是ReflectionJavaPythonRuby都支持Reflection,都支持Method(方法)對象。那麼咱們就利用Method Reflection編寫一個可以解惑任何類的任何方法的Proxy/Advice/Interceptor

MethodInterceptor{ 

    around( method ){ 
        // 作些額外的工做 
        
       method.invoke(…); // 調用真正的對象方法 
       
        // 作些額外的工做 
    } 
}


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

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

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

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

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

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

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

2)靜態編譯時期,處理編譯後的字節碼。JavaPython之類的虛擬機語言都有一種中間代碼(Java的中間代碼叫作字節碼),AOP處理程序能夠分析字節碼,並直接產生字節碼形式的Proxy。這種方式也叫作靜態字節碼加強。AspectJ也支持這種方式。Java有一些開源項目,好比 ASMCglib等,能夠分析並生成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的便利程度,可能要超過函數式編程語言。

相關文章
相關標籤/搜索