Java平臺AOP技術研究

第三部分  Java平臺AOP技術研究

3.1 Java平臺AOP技術概覽

3.1.1 AOP技術在Java平臺中的應用

AOP在實驗室應用和商業應用上,Java平臺始終走在前面。從最初也是目前最成熟的AOP工具——AspectJ,到目前已經融和在企業級容器JBoss中的JBoss AOP,均建立在Java平臺上。

前面已經描述到,AOP的目的就是將核心關注點和橫切關注點分離,實際上這就是一種分散關注(seperation of concerns)的思路。在Java平臺下,如果要開發企業級的應用,非J2EE莫屬。一個J2EE應用系統只有部署在J2EE容器中才能運行,那麼爲 什麼要劃分爲J2EE容器和J2EE應用系統? 通過對J2EE容器運行機制的分析,我們發現:實際上J2EE容器分離了一般應用系統的一些通用功能,例如事務機制、安全機制以及對象池或線程池等性能優 化機制。這些功能機制是每個應用系統幾乎都需要的,因此可以從具體應用系統中分離出來,形成一個通用的框架平臺,而且,這些功能機制的設計開發有一定難 度,同時運行的穩定性和快速性都非常重要,必須經過長時間調試和運行經驗積累而成,因此,形成了專門的J2EE容器服務器產品,如Tomcat JBoss、Websphere、WebLogic等。

從J2EE將應用系統和容器分離的策略,我們能夠看到AOP的影子。J2EE應用系統就相當於AOP技術中的核心關注點,它的內容主要包括企業系統 的商業邏輯;而J2EE容器則類似於橫切關注點,實現的是通用的功能機制。不過業界在選擇J2EE容器時,對於EJB這種重量級容器服務器而言,雖然欣賞 其高效、穩定及企業級的容器服務,但對於整個容器的高開銷、高成本以及過於複雜的解決方案均深懷戒心。因此,隨着J2EE的逐步演化,“輕量級容器架構” 通過開源社區如激流一般的驅動力,逐漸佔據了J2EE技術的強勢地位。而所謂“輕量級容器”與EJB提供的重量級架構的區別,就在於藉助了AOP技術和 IoC(Inversion of Control,反轉模式)機制,降低了代碼對於專用接口的依賴性,以簡短、輕便、專注、可移植的方式實現業務對象。事實上,我們看到的美好前景是,如果 所有企業級服務都可以通過AOP機制提供給普通Java對象,那麼深盔重鎧的應用服務器就不再有存在的價值了。

正是看到了AOP技術在企業級開發中的巨大潛力,而“輕量級容器”也喚起了改革EJB容器的呼聲(事實上,最新的 EJB V3.0 標準就使用了輕量級容器模型),越來越多的AOP工具在Java平臺下應運而生,從而形成了目前AOP工具百家爭鳴的局面。其中,應用最爲廣泛的主要包括 AspectJ、Spring AOP和JBoss AOP等。

3.1.2 Java平臺下AOP工具的比較

AOP是一項新技術,而在Java平臺下實現該技術的工具也非常多。雖然AOP的技術要素從本質上來講是一致的,但各種工具的實現方法也各有不同,本節基於AOP的技術要素,對當前應用較廣泛的AspectJ、Spring AOP和JBoss AOP進行比較。

3.1.2.1 AOP實現機制的區別

同樣是實現AOP,且AOP的技術要素完全相同,但各種AOP工具對於AOP實現的底層機制卻是不盡相同的。

AspectJ採用了源代碼生成技術來實現AOP。它提供了一套獨有的基於Java平臺的AOP語法,以及專有的AspectJ編譯器。編譯器在編 譯具有AspectJ語法的Java程序時,能夠識別諸如aspect,pointcut等特殊關鍵字,然後利用靜態織入的方式,修改需要被截取的方法所 屬類的源代碼,把advice或者introduce的業務邏輯代碼注入到正確的位置。利用AspectJ,可以將核心關注點完全獨立出來,然後通過 AspectJ語法,編寫符合核心關注點要求的橫切關注點代碼,最後通過AspectJ編譯器,將這兩者在後期結合起來。採用這種靜態織入技術,使得運用 了AOP技術的系統在運行性能上未受到任何損失,因爲它沒有利用反射技術或代理技術,而僅僅是程序的靜態擴展而已。然而這種源代碼生成方式實現的AOP雖 然在性能上具備一定的優勢,但它同時會給開發帶來一定的問題。例如代碼的後期修改會給系統帶來不可估量的影響。

Spring AOP是Spring框架中的一部分,但可以作爲一個獨立的模塊單獨存在。Spring AOP實現AOP技術從本質上來講,是利用了JDK提供的動態代理技術。而從實際的實現方式來看,則是利用了IoC(Inversion of Control,反轉模式)機制,同時採用了AOP聯盟(AOP Alliance)的通用AOP接口。首先,Spring AOP通過xml配置文件配置了pointcut,並利用Interceptor(攔截機)作爲設定的觸發條件。Interceptor是由用戶自定義 的,它相當於是AOP中的advice,但該Interceptor需要實現AOP聯盟的通用AOP接口,例如 org.aopalliance.intercept.MethodInterceptor。最後定義一個Spring AOP ProxyFactory用於加載執行AOP組件,並利用IoC機制將advice注入到接口以及實現類中。

JBoss 4.0提供了AOP框架。與Spring一樣,這個框架可與JBoss應用服務器緊密結合,也可以單獨運行在自己的應用中。JBoss AOP同樣需要Interceptor...***來完成對方法的攔截,它要求所有的Interceptor都必須實現 org.jboss.aop.Interceptor接口。在這個接口中最重要的方法就是invoke()。該方法對元數據直接進行操作,並利用反射的原 理去攔截方法的消息。Interceptor相當於AOP的advice,至於pointcut,則在xml配置文件中配置。可以看出,Spring AOP和JBoss AOP在實現上屬於動態織入的方式,它們與AspectJ在實現上是迥然不同的兩種方式。

3.1.2.2 關於“Aspect(方面)”的區別

在對aspect的尦??上,可以使用類似Java的代碼,註釋或xml。考慮一個常用的例子,對Account類的授權策略,如果以AOP技術來實現,運用不同的AOP工具,它們在方面聲明技術上的差異,是顯而易見的。

Aspect 中的方面聲明類似於 Java 語言中的類尦??,如圖3.1 所示。


圖3.1 AspectJ中的方面聲明

由於 AspectJ 是 Java 語言語法和語義的擴展,所以它提供了自己的一套處理方面的關鍵字。除了包含字段和方法之外,AspectJ 的方面尦??還包含pointcut和advice成員。示例中的pointcut使用了修飾符(modifier)和通配符(wildcard)模式來表 達“所有公共方法”。對帳戶的訪問,由 pointcut 參數提供。advice使用這個參數,而pointcut則用 this(account) 把它綁定。這樣做的效果,就是捕獲了正在執行的方法所隸屬的Account對象。否則,advice的主體與方法的主體相似。advice可以包含認證代 碼,或者就像在這個示例中一樣,可以調用其他方法。

JBoss AOP 基於 XML 的風格來聲明方面,如圖 3.2 所示。


圖3.2 JBoss AOP的方面尦??

在 XML 風格中,aspect、pointcut和advice的尦??都以 XML 形式表示的。advice的實現,用的是普通的 Java 方法,由JBoss AOP框架調用。pointcut和pointcut到advice的綁定都在方面中用XML註釋聲明。JBoss 沒有顯式地綁定 Account 參數,而是提供了對當前正在執行的對象的反射訪問,因此需要把類型轉換到對應的類型。JBoss AOP還可以通過標籤的方式對方面進行聲明。標籤均以“@”字符開始,它的使用有點類似於.Net中的Attribute。

Spring AOP同樣是基於 XML 的風格來聲明方面,如圖3.3所示。


圖3.3 Spring AOP的方面尦??

與JBoss AOP類似,Spring的advice實現是帶有特殊參數的Java方法,由 Spring 框架調用。XML描述accountBean,Spring框架通過它訪問 Account 對象,包括通知使用的...*** advisor 及其匹配模式,還有應用到模式的向前(before) 通知。

由於Spring AOP利用了IoC機制,因此比較JBoss AOP而言,在xml配置文件中提供了更加精細的配置。而構建、運行和配置 Spring AOP 方面的過程則與JBoss AOP基本相同,不過Spring AOP依賴的是Spring框架方便的、最小化的運行時配置,所以不需要獨立的啓動器。

3.1.2.3 語言機制的區別

    由於實現機制和語法風格的不同,三種AOP工具在語言機制上也有很大的不同,以下從四個方面來描述AspectJ、JBossAOP和Spring AOP之間的區別。

(1)pointcut匹配和複合:AspectJ和 JBoss AOP 提供了類似的類型模式支持。它們都允許簽名方面的匹配,對於 Java 5 應用程序來說,這些匹配包括註釋和泛型。AspectJ提供了一種簡潔的引用多個類型的技術(例如 Account+ 表示帳戶的所有子類型)。所有的工具都支持通配符匹配。Spring AOP 還提供了對正則表達式的支持。雖然這看起來可能是一個強大的優勢,但還是要指出其他技術已經選擇了放棄正則表達式,好讓pointcut讀起來不是太難, 同時不會存在潛在的損害。pointcut複合操作符基本上都是相同的。Spring AOP 不提供“非”操作,這個操作通常與沒有在 Spring AOP 連接點模型的容器(containment)連接點結合使用。

(2)advice形式:AspectJ 支持比其他技術更多的advice形式,而 JBoss AOP 只支持一種advice形式。每種通知形式都可以表達成 around advice,所以 JBoss 的技術是無限的,而且它確實提供了額外的簡單性。不好的一面是它損失了簡潔性。另外,強迫advice去遵守普通的 Java 規則(就像註釋和 XML 風格做的那樣),在一些情況下容易出問題,因爲這些規則是爲方法設計的。AspectJ 擁有把被通知方法的異常“軟化”的能力,這很有用,但是不符合方法異常檢測的標準語義。

(3)join point上下文:在 AspectJ中,通過指定和綁定pointcut參數訪問動態連接點的狀態,類似於在 Java 語言中聲明方法參數的技術(請參閱圖3.1)。這爲連接點上下文提供了靜態類型化的好處。JBoss AOP 和 Spring AOP 反射性地訪問連接點的狀態,這消除了在切入點表達式中參數綁定的複雜性,代價是參數靜態類型化。Java 程序員習慣了方法參數靜態類型化帶來的好處,同時還可以從pointcut參數的靜態類型化得到同樣的好處。所以,在 JBoss AOP 最近的發行版本中,有提供靜態類型化的“args”的計劃。

(4)擴展性:aspect的擴展性支持庫方面的部署,這樣可以在日後爲特定程序將這些庫方面具體化。例如,一個方面庫可以提供應用程序監視需要的 全部邏輯和基礎設施。但是,要採用某個特定項目的庫,那麼庫使用的pointcut必須擴展成應用程序特定的join point。AspectJ 用抽象方面支持擴展性,抽象方面包含抽象的pointcut和具體的advice。擴展抽象方面的子方面必須具體化pointcut。JBoss AOP 使用了完全不同的技術,沒有使用抽象切入點機制。擴展是通過生成aspect的子類、並在 XML 中或通過註釋定義新的advice綁定而實現的。pointcut到advice的顯式綁定爲JBoss AOP提供了顯著優勢,從而可以很容易地把方面擴展到新系統,無需要生成子類。

3.2 Java平臺下AOP主流工具研究

3.2.1 AsepctJ研究

AspectJ作爲Java編程語言擴展的AOP工具,使得我們運用AOP技術能夠像普通的Java編程那樣,特殊之處,僅在於我們需要使用AspectJ提供的特殊語法。接下來,我將通過一些實例,介紹如何運用AspectJ實現AOP技術。

3.2.1.1 AspectJ語言特性

設定我們的開發項目中需要應用到日誌記錄,根據前面介紹的AOP知識,我們已經能夠從這個需求中識別出橫切關注點——日誌記錄。因此,我們需要定義關於“日誌記錄”的aspect:

AspectJ語言:

public aspect AutoLog

    pointcut publicMethods() : execution(public * org.apache.cactus..*(..));
    pointcut logObjectCalls() : execution(* Logger.*(..));
    pointcut loggableCalls() : publicMethods() && ! logObjectCalls(); 

    before() : loggableCalls()
    {
      Logger.entry(thisJoinPoint.getSignature().toString());
    }
    after() : loggableCalls()
    {
      Logger.exit(thisJoinPoint.getSignature().toString());
    }
}

如果僅僅熟悉Java編程,會發現有很多關鍵字是Java語言中不曾包含的,它們均是AspectJ提供的。

分析上述的代碼,首先是aspect的聲明,它類似於Java中的類聲明,定義了一個aspect:AutoLog。在這個方面中分別包含了pointcut和advice。

pointcut共有三個:publicMethod、logObjectCalls和loggableCalls。publicMethod將選 擇org.apache.cactus包中的所有公共(public)方法的執行。所謂“選擇”,就意味着它的join point爲其選擇的方法。當這些方法被調用時,就會執行pointcut的advice代碼。而在pointcut中,execution 是一個原始的 Pointcut(就象 int 是一種原始的 Java 類型)。它選擇與括號中定義的方法說明匹配的任何方法的執行。方法說明允許包含通配符。logObjectCalls的pointcut則選擇 Logger 類中的所有方法的執行。第三個pointcut比較特殊,它使用&& !合併了前兩個 Pointcut,這意味着它選者了除Logger類中的公共方法以外, org.apache.cactus 中所有的公共方法。

advice在aspect中,被用來完成實際的日誌紀錄。advice有三種,分別爲before、after和around。如上述代碼中定義的advice:

AspectJ語言
before() : loggableCalls()
{
    Logger.entry(thisJoinPoint.getSignature().toString());
}

該advice的定義表示的含義是,如果org.apache.cactus中所有的公共方法(Logger類的公共方法除外)被執行,則在這些方法執行之前,需要先執行該advice定義的邏輯。

3.2.1.2 AspectJ的高級語言特性

在本文第二部分介紹AOP技術時,提到了橫切技術的分類。其中,靜態橫切技術能夠擴展一個對象的結構。使用引入 (Introduction),Aspect 可以向類中添加新的方法和變量、聲明一個類實現一個接口或將檢查異常轉換爲未檢查異常(unchecked exception)。

3.2.1.2.1 向現有類添加變量和方法

假設您有一個表示持久存儲的數據緩存的對象。爲了測量數據的“更新程度”,您可能決定向該對象添加時間戳記字段,以便容易地檢測對象是否與後備存儲 器同步。由於對象表示業務數據,根據AOP的知識,我們應該將這種機制性細節從對象中隔離。使用 AspectJ,可以用如下代碼中所顯示的語法來向現有的類添加時間戳記:

AspectJ語言
public aspect Timestamp
{
    private long ValueObject.timestamp;
    public long ValueObject.getTimestamp()
    {
       return timestamp;
    }
    public void ValueObject.timestamp()
    {     
       this.timestamp = System.currentTimeMillis();
    }
}

 

通過introduction,我們就非常方便的爲ValueObject類型添加了timestamp的變量和相關方法。除了必須限定在哪個類上聲明引入的方法和成員變量以外,聲明引入的方法和成員變量幾乎與聲明常規類成員相同。

3.2.1.2.2實現多繼承功能

利用introduction,AspectJ允許向接口和類添加成員,也突破了Java語言只能單繼承的限制,允許程序按C++方式那樣實現多繼 承。如果您希望上述的aspect Timestamp能夠泛化 (generalize),以便能夠對各種對象重用時間戳記代碼,可以定義一個稱爲 TimestampedObject 的接口,並使用引入(Introduction)來將相同成員和變量添加到接口而不是添加到具體類中,如下所示:

AspectJ語言:
public interface TimestampedObject
{
    long getTimestamp();
    void timestamp();
}
public aspect Timestamp
{
    private long TimestampedObject.timestamp;
    public long TimestampedObject.getTimestamp()
    {
        return timestamp;
    }
    public void TimestampedObject.timestamp()
    {
        this.timestamp = System.currentTimeMillis();
    }
}

Timestamp方面由於在TimestampedObject接口中引入(introduction)了方法的實現,使得 TimestampedObject接口改變其本質,成爲了一個特殊的類類型。特殊之處就在於一個已經繼承了一個類的類類型,通過AspectJ的語法, 仍然可以再次繼承TimestampedObject,這就間接地實現了類的多繼承。而這個特殊的AspectJ語法就是declare parents語法。declare parents和其它AspectJ 類型表達一樣,可以同時應用於多個類型:

AspectJ語言
declare parents: ValueObject || BigValueObject implements TimestampedObject;

3.2.1.3 編譯器及工具支持

    要讓aspect能夠正常工作,必須將aspect加入到它們要修改的代碼中去。這項工作由AspectJ提供的ajc編譯器完成。ajc 編譯器用來編譯類和 Aspect 代碼。ajc 既可以作爲編譯器也可以作爲預編譯器操作,生成有效的 .class 或 .java 文件,可以在任何標準 Java 環境(添加一個小的運行時 JAR)中編譯和運行這些文件。

要使用 AspectJ 進行編譯,將需要顯式地指定希望在給定編譯中包含的源文件(Aspect 和類),ajc不象javac那樣簡單地爲相關導入模塊搜索類路徑。之所以這樣做,是因爲標準 Java 應用程序中的每個類都是相對分離的組件。爲了正確操作,一個類只要求其直接引用的類的存在。Aspect 表示跨越多個類的行爲的聚集。因此,需要將 AOP 程序作爲一個單元來編譯,而不能每次編譯一個類。

AspectJ 當前版本的一個重要限制是其編譯器只能將aspect加入到它擁有源代碼的代碼中。也就是說,不能使用ajc將Advice添加到預編譯類中。 AspectJ 團隊認爲這個限制只是暫時的,AspectJ 網站承諾未來的版本(正式版 2.0)將允許字節碼的修改。

AspectJ發行版包含了幾種開發工具。這預示着 AspectJ 將有美好的前景,因爲它表明了作者對這一部分的一個重要承諾,使 AspectJ 對於開發人員將是友好的。對於面向 Aspect 的系統工具支持尤其重要,因爲程序模塊可能受到它們所未知的模塊所影響。

隨 AspectJ 一起發佈的一個最重要的工具是圖形結構瀏覽器,它展示了 Aspect 如何與其它系統組件交互。這個結構瀏覽器既可以作爲流行的 IDE 的插件,也可以作爲獨立的工具。圖3.4顯示了先前討論的日誌記錄示例的視圖。


圖3.4 AspectJ提供的“結構瀏覽器”工具

除了結構瀏覽器和核心編譯器之外,您還可以從 AspectJ 網站下載一個 Aspect 支持的調試器、一個javadoc工具、一個Ant任務以及一個Emacs 插件。

3.2.2 JBoss AOP研究

JBoss AOP關於AOP的實現與AspectJ是兩種完全不同的風格。由於Java利用元數據來存儲有關類型、方法、字段的相關信息,因此,可以通過Java提供的反射功能獲得模塊相關的元數據,對方法進行攔截,並將被攔截的方法與aspect邏輯進行關聯。

3.2.2.1 ***(Interceptor)

在JBoss AOP中,是用***來實現advice的。可以自定義***,攔截方法調用、構造函數調用以及對字段的訪問,但JBoss要求這些自定義的***,必須實現org.jboss.aop.Interceptor接口:

Java語言:
public interface Interceptor
{
    public String getName();
    public InvocationResponse invoke(Invocation invocation) throws Throwable;
}

 

在JBoss AOP中,被攔截的字段、構造器和方法均被轉化爲通用的invoke方法調用。方法的參數接收一個Invocation對象,而方法 的返回值、字段的存取以及構造函數則被填入一個InvocationResponse對象。Invocation對象同時還驅動攔截鏈。下面我們自定義一 個***,它能夠攔截構造函數和方法的調用,並將跟蹤信息打印到控制檯上:

Java語言
import org.jboss.aop.*;
import java.lang.reflect.*;

public class TracingInterceptor implements Interceptor
{
    public String getName()
    {
        return TracingInterceptor;
    }
    public InvocationResponse invoke(Invocation invocation) throws Throwable
    {
        String message = null;
        if (invocation.getType() == InvocationType.METHOD)
        {
            Method method = MethodInvocation.getMethod(invocation);
            message = method: + method.getName();
        }
        else
        {
            if (invocation.getType() == InvocationType.CONSTRUCTOR)
            {
                Constructor c = ConstructorInvocation.getConstructor(invocation);
                message = constructor: + c.toString();
            }
            else
            {
                // 不對字段作處理,太繁瑣;
                return invocation.invokeNext();
            }
            System.out.println(Entering + message);
        }
        // 繼續。調用真正的方法或者構造函數
        InvocationResponse rsp = invocation.invokeNext();
        System.out.println(Leaving + message);
        return rsp;
    }
}

在自定義的TracingInterceptor類中,invoke()方法對invocation的類型作判斷,以根據方法、構造函數和字段類 型,分別作出不同的操作。而其中,invocation.invokeNext()則表示通過一個攔截鏈獲得下一個invocation。

定義的攔截必須在xml文件配置,使其綁定到具體的類。這個定義即爲AOP中的切入點pointcut。例如具體的類爲BusinessObject,則該pointcut在xml中的定義如下:

<?xml version="1.0" encoding="UTF-8">
<aop>
    <interceptor-pointcut class="BusinessObject">
        <interceptors>
            <interceptor class="TracingInterceptor" />
        </interceptors>
    </interceptor-pointcut>
</aop>

上面的pointcut綁定TracingInterceptor到一個叫做BusinessObject的類。如果要將該Interceptor 綁定到多個類,還可以利用正則表達式。例如,如果你想綁定由JVM載入的類,類表達式將變爲 .*。如果你僅僅想跟蹤一個特定的包,那麼表達式將是bruce.zhang.mypackge.*。

當JBoss AOP獨立運行時,任何符合 META-INF/jboss-aop.xml模式的XML文件將被JBoss AOP 運行期程序載入。如果相關的路徑被包含在任何JAR或你的CLASSPATH目錄中,該XML文件將在啓動時,由JBoss AOP 運行期程序載入。

JBoss AOP還提供了過濾功能,可以通過在xml文件中配置過濾的標誌,使一些特定的方法(包括字段的訪問)被過濾,從而不再執行Interceptor的相關 邏輯。例如,我們要過濾BusinessObject類的所有get()和set()方法,以及main()方法,則可以修改上述的xml文件:

<?xml version="1.0" encoding="UTF-8">
<aop>
    <class-metadata group="tracing" class=" BusinessObject ">
        <method name="(get.*)|(set.*)">
            <filter>true</filter>
        </method>
        <method name="main">
            <filter>true</filter>
        </method>
    </class-metadata>
</aop>

相應的,Interceptor代碼也應作相關的修改,使其能夠識別配置文件中的filter屬性:

Java語言
public class TracingInterceptor implements Interceptor
{
    //getName()方法略;
    public InvocationResponse invoke(Invocation invocation) throws Throwable
    {
        String filter=(String)invocation.getMetaData(tracing, filter);
        if (filter != null && filter.equals(true))
            return invocation.invokeNext();
        //後面的代碼略;
    }
}

3.2.2.2 引入(Introduction)

JBoss AOP同樣提供introduction功能,通過它,就可以爲現有的類引入第三方接口或類的API了。例如,我們可以爲具體的類如 BusinessObject提供Tracing的開關,使得BusinessObject對象能夠根據具體的情況打開或關閉aspect的 Tracing功能。爲實現該功能,可以定義一個Tracing接口:

Java語言
public interface Tracing
{
    void enableTracing();
    void disableTracing();
}

接下來需要定義一個混合類,它實現了接口Tracing。當BusinessObject類被實例化時,該混合類的實例就會被綁定到BusinessObject上。實現方法如下:

Java語言
import org.jboss.aop.Advised;

public class TracingMixin implements Tracing
{
    Advised advised;

    Public TracingMixin(Object obj)
    {
        this.advised = (Advised)obj;
    }
    public void enableTracing()
    {
        advised._getInstanceAdvisor().getMetaData().addMetaData("tracing""filter",true);
    }
    public void disableTracing()
    {
        advised._getInstanceAdvisor().getMetaData().addMetaData("tracing""filter",false);
    }
}

enableTracing()方法將filter屬性綁定到對象實例。disableTracing()方法作同樣的事,但是將filter屬性設置爲false。

定義了Tracing接口和實現了該接口的混合類後,就可以在xml文件中定義一個pointcut,強制BusinessObject類實現Tracing接口:

XML語言
<?xml version="1.0" encoding="UTF-8">
<aop>
    <introduction-pointcut class="BusinessObject">
        <mixin>
            <interfaces>Tracing</interfaces>
            <class>TracingMixin</class>
            <construction>new TracingMixin(this)</construction>
        </mixin>
    </introduction-pointcut>
</aop>

注意xml文件中的標籤,它代表的含義是當BusinessObject對象被實例化時,將執行該標籤內的代碼。以本例而言,當創建BusinessObject對象時,一個TracingMixin類的實例將被創建。任何單行的Java代碼都可以放到標籤中。

通過“引入(introduction)”功能,在處理BusinessObject對象時,就可以視其爲Tracing接口類型而進行操作了,如下的示例:

Java語言
public class BusinessObject
{
    public BusinessObject () {}
    public void helloWorld() { System.out.println(Hello World!); }

    public static void main(String[] args)
    {
        BusinessObject bo = new BusinessObject ();
        Tracing trace = (Tracing)this;
        bo.helloWorld();
       
        System.out.println("Turn off tracing.");
        trace.disableTracing();
        bo.helloWorld();
       
        System.out.println("Turn on tracing.");
        trace.enableTracing();
        bo.helloWorld();
    }
}

注意如下代碼:

        Tracing trace = (Tracing)this;       

 

此時this代表的即爲BusinessObject,從Java代碼的角度來看,由於BusinessObject並沒有實現Tracing接 口,因此這行代碼所示的顯式轉換爲Tracing類型是不成功的。但通過“引入”功能,使得BusinessObject通過混合類,實現了 Tracing接口,從而使得如上的代碼能夠順利執行。隱含的意義就是,我們沒有修改BusinessObject的定義,而是通過AOP技術,爲 BusinessObject擴展實現了第三方提供的接口Tracing。

3.2.3 Spring AOP研究

Spring AOP使用純Java實現,不需要特別的編譯過程,也不需要控制類裝載層次。與JBoss AOP相同,它仍然利用了***完成對方法的攔截。然而,Spring AOP實現AOP的主要技術卻主要來自於AOP聯盟,如***應實現 org.aopalliance.intercept.MethodInterceptor 接口,而所有advice必須實現org.aopalliance.aop.Advice標籤接口。此外,Spring實現AOP的目標也不同於其他大部 分AOP框架,它的目標不是提供及其完善的AOP實現,而是提供一個和Spring IoC緊密整合的AOP實現,幫助解決企業應用 中的常見問題。因此,Spring AOP的功能通常是和Spring IoC容器聯合使用的。AOP Advice是用普通的bean定義語法來定義的,Advice和pointcut本身由Spring IoC 管理。這是一個重要的其他AOP實現的區別。

3.2.3.1 切入點(pointcut)

Spring的切入點模型能夠使pointcut獨立於advice類型被重用。同樣的pointcut有可能接受不同的advice。將 Pointcut接口分成兩個部分有利於重用類和方法的匹配部分,並且組合細粒度的操作(如和另一個方法匹配器執行一個“並”的操作)。

在Spring的切入點中,org.springframework.aop.Pointcut接口是重要的接口,它用來指定通知到特定的類和方法目標。完整的接口定義如下:

Java語言
public interface Pointcut
{
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

ClassFilte類型也是一個接口,該接口被用來將切入點限制到一個給定的目標類的集合。 如果matches()永遠返回true,所有的目標類都將被匹配。

Java語言:
public interface ClassFilter
{
    boolean matches(Class clazz);
}

MethodMatcher接口通常更加重要。完整的接口定義如下:

Java語言
public interface MethodMatcher
{
    boolean matches(Method m, Class targetClass);
    boolean matches(Method m, Class targetClass, Object[] args);
    boolean isRuntime();
}

matches(Method, Class) 方法被用來測試這個切入點是否匹配目標類的給定方法。這個測試可以在AOP代理創建的時候執行,避免在所有方法調用時都需要進行 測試。如果2個參數的matches()方法對某個方法返回true,並且MethodMatcher的isRuntime()也返回true,那麼3個 參數的matches()方法將在每次方法調用的時候被調用。這使切入點能夠在目標advice被執行之前立即查看傳遞給方法調用的參數。由於大部分 MethodMatcher都是靜態的,意味着isRuntime()方法會返回false。此種情況下,3個參數的matches()方法永遠不會被調 用。

Spring AOP提供了幾個實用的切入點實現,其中較爲常用的是正則表達式切入 點:org.springframework.aop.support.RegexpMethodPointcut,它使用Perl 5的正則表達式的語法。使用這個類你可以定義一個模式的列表。如果任何一個匹配,那個切入點將被計算成 true。用法如下:

XML語言
<bean id="settersAndAbsquatulatePointcut"
    class="org.springframework.aop.support.RegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*get.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

不過,更多情況下是直接使用RegexpMethodPointcut一個實用子類: RegexpMethodPointcutAdvisor。它允許我們同時引用一個advice(在Spring AOP中,advice可以是***,也可以是before advice,throws advice等)。這就簡化了bean的裝配,因爲一個bean可以同時當作pointcut和advice,如下所示:

XML語言
<bean id="myPointcutAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref local="MyInterceptor" />
    </property>
    <property name="patterns">
        <list>
            <value>.*save.*</value>
            <value>.*do.*</value>
        </list>
    </property>
</bean>

注意配置文件中的myPointcutAdvisor,在Spring AOP中,一個advisor就是一個aspect完整的模塊化表示。通過advisor,可以將pointcut和advice(在此處即爲MyInterceptor)綁定起來。

3.2.3.2 通知(advice)

Spring AOP的advice可以跨越多個被advice對象共享,或者每個被advice對象有自己的advice。要實現advice,最簡單的做法就是定義 一個***(Interceptor)。它採用了AOP聯盟(AOP Alliance)的通用AOP接口(接口定義爲aopalliance.jar)。要實現advice,需要實現aopalliance.jar中定義 的MethodInterceptor接口。

例如,我們定義了一個業務對象接口BusinessObject及其實現類BusinessObjectImpl,該業務對象能夠存儲數據,其定義如下:

Java語言:
public interface BusinessObject
{
    public void save();
}
public class BusinessObjectImpl implements BusinessObject
{
    public void save()
    {
         System.out.println("saving domain object......");
    }
}

現在需要爲業務對象BusinessObject的Save()方法,提供Lock機制。根據Spring AOP的實現方式,我們可以定義一個LockInterceptor來實現MethodInterceptor接口:

Java語言
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LockInterceptor implements MethodInterceptor
{
    public Object invoke(MethodInvocation invocation) throws Throwable
    {
        // TODO Auto-generated method stub
        lock();
        Object ret= invocation.proceed();
        unlock();
        return ret;
    }
    private void lock()
    {
        System.out.println("lock domain object...");
    }
    private void unlock()
    {
        System.out.println("unlock domain object...");
    }
}

爲將interceptor與具體的advice綁定起來,需要在配置文件中配置bean:

XML語言:
<bean id="MyInterceptor" class="test.aop.spring.LockInterceptor"/>

3.2.3.3 AOP代理與IoC容器

由於Spring中提供了IoC容器(例如BeanFactory),因此我們可以通過Ioc機制,利用ProxyFactoryBean來創建 AOP代理。ProxyFactoryBean和其他Spring的 FactoryBean實現一樣,引入一個間接的層次。如果你定義一個名字爲foo的ProxyFactoryBean,引用foo的對象所看到的不是 ProxyFactoryBean實例本身,而是由實現ProxyFactoryBean的類的 getObject()方法所創建的對象。這個方法將創建一個包裝了目標對象 的AOP代理。

AOP代理利用的是Java的動態代理技術,通過它就可以加載並執行AOP組件。同時,還需要通過IoC的方式將advice注入到接口以及其實現類。以前面的業務對象BusinessObject爲例,在xml配置文件中的配置如下:

XML語言
<bean id="myAOPProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces">
       <value>test.aop.spring.BusinessObject</value>
    </property>
    <property name="target">
       <ref local="impl" />
    </property>
    <property name="interceptorNames">
       <value>myPointcutAdvisor</value>
    </property>
</bean>
<bean id="impl" class="test.aop.spring.BusinessObjectImpl"/>

通過上述對pointcut、advice、advisor和AOP代理的配置,我們就可以輕易地在Spring中實現AOP,例如:

Java語言
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class App
{
    private BusinessObject bo = null;
    public static void main(String[] args)
    {
        ApplicationContext ctx=new FileSystemXmlApplicationContext("Bean.xml");
        bo= (BusinessObject) ctx.getBean("myAOPProxy");
        bo.save();
    }
}

首先,通過AOP代理獲得BusinessObject對象。當調用BusinessObject對象的save()方法時,*** LockInterceptor根據RegexpMethodPointcutAdvisor配置的pointcut和advice之間的關係,判定該方 法的調用爲join point,從而攔截該方法調用,並注入advice的執行邏輯,即lock()和unlock(),最終實現了AOP。

3.2.3.4 引入(introduction)

在Spring AOP中,將introduction當作advice來處理。與一般的advice一樣,introduction advice相當於一種特殊類型的攔截通知,需要實現IntroductionAdvisor和IntroductionInterceptor接口,而 IntroductionInterceptor接口繼承自MethodInterceptor:

Java語言
public interface IntroductionInterceptor extends MethodInterceptor
{
    boolean implementsInterface(Class intf);
}

Introduction通知不能被用於任何pointcut,因爲它只能作用於類層次上,而不是方法。我們可以只用InterceptionIntroductionAdvisor來實現導入通知,它有下面的方法:

Java語言
public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor
{
    ClassFilter getClassFilter();
    IntroductionInterceptor getIntroductionInterceptor();
    Class[] getInterfaces();
}

接下來,我以JBoss AOP一節中的例子來說明introduction在Spring AOP中的應用。我們的目標仍然是爲一個已有的業務對象引入第三方接口Tracing:

Java語言:
public interface Tracing
{
    void enableTracing();
    void disableTracing();
    boolean enabled();
}

首先,我們需要一個做大量轉化的IntroductionInterceptor。在這裏,我們繼承 org.springframework.aop.support.DelegatingIntroductionInterceptor 實現類。當然我們可以直接實現IntroductionInterceptor接口,但是大多數情況下 DelegatingIntroductionInterceptor是最合適的。

DelegatingIntroductionInterceptor的設計是將introduction委託到真正實現introduction 接口的接口,隱藏完成這些工作的***。委託可以使用構造方法參數設置到任何對象中;默認的委託就是自己(當無參數的構造方法被使用時)。這樣在下面的例 子裏,委託是DelegatingIntroductionInterceptor的子類 TracingMixin。給定一個委託(默認是自身)的 DelegatingIntroductionInterceptor實例尋找被這個委託(而不是IntroductionInterceptor)實現 的所有接口,並支持它們中任何一個導入。子類如TracingMixi也可能調用suppressInterflace(Class intf) 方法來隱藏不應暴露的接口。然而,不管IntroductionInterceptor 準備支持多少接口,IntroductionAdvisor將控制哪個接口將被實際暴露。一個導入的接口將隱藏目標的同一個接口的所有實現。

這樣,TracingMixin繼承DelegatingIntroductionInterceptor並自己實現接口Tracing。父類自動選擇支持introduction的Tracing,所以我們不需要指定它。用這種方法我們可以導入任意數量的接口。

Java語言
public class TracingMixin extends DelegatingIntroductionInterceptor implements Tracing
{
    private boolean enabled;
    public void enableTracing ()
    {
        this.enabled = true;
    }

    public void disableTracing ()
    {
        this. enabled = false;
    }

    public boolean enabled()
    {
        return this.enabled;
    }
    public Object invoke(MethodInvocation invocation) throws Throwable
    {      
        return super.invoke(invocation);
    }
}

通常不要需要改寫invoke()方法:實現DelegatingIntroductionInterceptor就足夠了,如果是引入的方法,DelegatingIntroductionInterceptor實現會調用委託方法, 否則繼續沿着連接點處理。

所需的introduction advisor是很簡單的。只需保存一個獨立的TracingMixin實例,並指定導入的接口,在這裏就是Tracing。此時,TracingMixin沒有相關配置,所以我們簡單地使用new來創建它。

Java語言:
public class TracingMixinAdvisor extends DefaultIntroductionAdvisor
{
    public TracingMixinAdvisor() {
        super(new TracingMixin(),Tracing.class);
    }
}

 

我們可以非常簡單地使用這個advisor。它不需要任何配置。(但是,有一點是必要的:就是不可能在沒有IntroductionAdvisor 的情況下使用IntroductionInterceptor。) 和引入一樣,通常 advisor必須是針對每個實例的,並且是有狀態的。我們會有不同的TracingMixinAdvisor。每個被通知對象,會有不同的 TracingMixin。advisor組成了被通知對象的狀態的一部分。

在Spring中,Spring AOP的核心API已經基本穩定了。和Spring的其它部分一樣, AOP框架是模塊化的,在保留基礎設計的同時提供擴展。在Spring 1.1到1.2階段有很多地方可能會有所提高,但是這些地方也保留了向後兼容性。它們是:

(一)性能的提高:AOP代理的創建由工廠通過策略接口處理。因此能夠支持額外的AOP 代理類型而不影響用戶代碼或核心實現。
(二)更具表達力的pointcut:Spring目前提供了一個具有表達力的切入點接口,同時添加了更多的切入點實現。Spring正在考慮提供一個簡單但具有強大表達式語言的實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章