天天看點

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 所示。

AOP技術研究——Java平台AOP技術研究

圖3.1 AspectJ中的方面聲明

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

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

AOP技術研究——Java平台AOP技術研究

圖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所示。

AOP技術研究——Java平台AOP技術研究

圖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:

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:

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,可以用如下代碼中所顯示的文法來向現有的類添加時間戳記:

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)來将相同成員和變量添加到接口而不是添加到具體類中,如下所示:

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 類型表達一樣,可以同時應用于多個類型:

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顯示了先前讨論的日志記錄示例的視圖。

AOP技術研究——Java平台AOP技術研究

圖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接口:

public interface Interceptor

{

    public String getName();

    public InvocationResponse invoke(Invocation invocation) throws Throwable;

}

在JBoss AOP中,被攔截的字段、構造器和方法均被轉化為通用的invoke方法調用。方法的參數接收一個Invocation對象,而方法的傳回值、字段的存取以及構造函數則被填入一個InvocationResponse對象。Invocation對象同時還驅動攔截鍊。下面我們自定義一個攔截器,它能夠攔截構造函數和方法的調用,并将跟蹤資訊列印到控制台上:

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屬性:

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接口:

public interface Tracing

{

    void enableTracing();

    void disableTracing();

}

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

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 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接口類型而進行操作了,如下的示例:

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接口是重要的接口,它用來指定通知到特定的類和方法目标。完整的接口定義如下:

public interface Pointcut

{

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

ClassFilte類型也是一個接口,該接口被用來将切入點限制到一個給定的目标類的集合。 如果matches()永遠傳回true,所有的目标類都将被比對。

public interface ClassFilter

{

    boolean matches(Class clazz);

}

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

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。用法如下:

<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,如下所示:

<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,該業務對象能夠存儲資料,其定義如下:

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接口:

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:

<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配置檔案中的配置如下:

<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,例如:

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:

public interface IntroductionInterceptor extends MethodInterceptor

{

    boolean implementsInterface(Class intf);

}

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

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor

{

    ClassFilter getClassFilter();

    IntroductionInterceptor getIntroductionInterceptor();

    Class[] getInterfaces();

}

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

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,是以我們不需要指定它。用這種方法我們可以導入任意數量的接口。

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來建立它。

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正在考慮提供一個簡單但具有強大表達式語言的實作。

上一篇: AOP技術研究
下一篇: IPv6技術研究