程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java面向方面編程概述

Java面向方面編程概述

編輯:關於JAVA

概述

面向方面的程序設計(AOP)是一個激動人心的新規范,和已經有十幾年歷史的面向對象的程序設計(OOP)在軟件開發上有相同的作用。 AOP和OOP不是相互競爭的技術,實際上它們相輔相成的十分融洽。面向對象的程序設計對於建模常見的對象等級體系非常有用。 它的不足之處在於處理跨多個非關聯對象模型的常見情況;這時就有AOP的用武之地了。AOP允許你跨關聯,使用單獨的、彼此之間非常不同的對象模型。 它允許你層次化--而不是嵌入--函數,以便代碼更易讀、更便於維護。 我們喜歡把面向對象的程序設計想象成為自頂向下的軟件開發,而面向方面的程序設計則是自左向右;它們是完全正交的技術,彼此之間相輔相成的十分融洽。

面向對象的程序設計的手段是繼承、封裝和多態性,而面向方面的程序設計的組件是通知/監聽器(advice/interceptor)、引入(introduction)、元數據(metadata)和切入點(pointcut) 。 讓我們看看這些定義。

通知/監聽器(advice/interceptor)

一個通知是被某一事件觸發的程序邏輯。 它是可以被插入一個方法調用者和實際的方法之間的行為。 通知實際上是面向方面的程序設計的關鍵。這些構造允許你定義橫切(cross-cutting)行為。通知允許你透明地應用象記錄和度量這樣的事到現有的對象模型中。

在JBoss AOP中,我們使用監聽器實現通知。你可以定義監聽器監聽方法調用、構造器調用和字段訪問。稍後,我們將研究如何應用這些監聽器到一個現有的對象模型中。

引入

引入是一種添加方法或者字段到一個現有類的方法。它們甚至允許你改變一個現有類目前實現的接口並且引入一個混合類實現這些新接口。

引入允許你把多繼承帶到簡單的Java類中。引入的一個重要的使用實例就是你有一個想有運行時間接口的方面。你想跨不同的對象層次應用你的方面,但是你仍然想應用程序開發者能夠調用指定方面API。

Apple apple = new Apple();
LoggingAPI logging = (LoggingAPI)apple;
Apple.setLoggingLevel(VERBOSE);

引入可以是一個把新API附加於一個現有對象模型的方法。

元數據

元數據是可以附屬於一個類的附加信息,或者以靜態方式或者在運行時間。當你可以動態地把元數據附上到一個對象給定的實例中的時候,它將更加有效。當你正在編寫可用於任何對象的一般的方面的時候,元數據顯得特別重要,但是程序邏輯必須知道指定類的信息。元數據被使用的一種很類似的情況是EJB規范。在EJB XML配置描述符中,你在一個每方法的基礎上定義事務屬性。應用程序服務器知道何時何地開始、暫停或者委托一個事務,因為你已經定義Required、RequiresNew、Supports等方法。在你的EJB類和事務管理程序綁定的元數據裡,是bean的XML配置文件。

C#已經把元數據構建入語言中。XDoclet是另一個正在工作的很好的元數據的例子。如果你曾經用過XDoclet來生成EJB文件和配置描述符,你肯定知道元數據強大的功能。Java Community Process(JCP)達成協議,元數據被添加進JDK 1.5 (見JSR175)。直到JSR 175真正成為一種規范,一個好的AOP框架才能提供一個機制,聲明在運行時間有效的類級元數據。

切入點

如果監聽器,引入和元數據是面向方面的程序設計的特性,那麼切入點就是把這些特性聯系起來的紐帶。切入點告訴面向方面的程序設計框架,哪個監聽器將和哪個類捆綁在一起,哪些元數據將用於哪些類,或者引入將被導入到哪些類中。 切入點定義能夠用於你的應用程序的類的各種面向方面的程序設計特性。

工作中的面向方面的程序設計

例子1、使用監聽器

JBoss 4.0帶有一個面向方面的程序設計框架。這個框架和JBoss應用程序服務器緊密地整合,但是你還可以在你自己的應用程序上單獨運行它。你只有看到它如何工作,才能真正明白一個概念,所以讓我們使用JBoss AOP中的例子來說明所有這些東西是如何合作的。在本文剩余的部分,我們將使用AOP構建一個簡單的追蹤框架。

定義一個監聽器

首先要做的是實現我們的小跟蹤框架,來定義將做實際工作的監聽器。 JBoss AOP中的所有的監聽器必須實現org.jboss.aop.Interceptor接口。

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

JBoss AOP中被監聽的所有字段、構造器和方法被轉化為一個普通的Invocation調用。方法參數被裝入一個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
{
// Do nothing for fields. Just too verbose.
return invocation.invokeNext();
}
System.out.println(Entering + message);
// Continue on. Invoke the real method or constructor.
InvocationResponse rsp = invocation.invokeNext();
System.out.println(Leaving + message);
return rsp;
}
}

上面的監聽器將監聽一個字段、構造器或者方法的所有調用。如果調用類型是一個方法或者構造器,那麼帶有方法或者構造器簽名的跟蹤信息將被輸出到控制台。

附加一個監聽器

好的,這樣我們就已經定義好監聽器了。但是我們如何把這個監聽器附加到一個實際的類中呢?為了實現這個目的,我們需要定義一個切入點(pointcut)。對於JBoss AOP來說,切入點在一個XML文件中被定義。讓我們來看看它看起來是什麼樣的。

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

上面的切入點把TracingInterceptor附加到一個名為POJO的類中。這似乎有點麻煩;我們必須為我們想跟蹤的每個類創建一個切入點嗎?幸運的是,監聽器-切入點的類屬性可以使用任何正則表達式。因此,如果你想追蹤每個JVM加載的類,類的表達式將變為 .*。 如果你只想追蹤某個特定的包,那麼表達式將是com.acme.mypackge.*。

當獨立運行JBoss AOP時,任何適合META-INF/jboss-aop.xml模式的XML文件都將在JBoss AOP運行時間載入。如果相對路徑被包含在任何JAR中或者目錄被包含在你的CLASSPATH中,特定的XML文件將在啟動時被JBoss AOP運行時間載入。

運行例程

我們將使用上面定義的切入點運行這個例程。POJO類如下。

public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojo.helloWorld();
}
}
TracingInterceptor將監聽main ()、POJO ()和helloWorld ()的調用。輸入為:
Entering method: main
Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
Leaving method: main

你可以到http://www.jboss.org/index.html?module=html&op=userdisplay&id=developers/projects/jboss/aop去下載JBoss AOP和例程代碼。 編譯和執行:

$ cd oreilly-aop/example1
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader POJO

JBoss AOP操作字節碼,附加到監聽器上。因為沒有編譯步驟,AOP運行時間必須全局控制ClassLoader。所以如果你在JBoss應用程序服務器以外運行的時候,你必須使用一個JBoss指定的classloader覆蓋系統classloader。

例2、使用元數據

TracingInterceptor不追蹤字段訪問,因為它有點太冗長。對於開發者來說,實現get()和set()方法來封裝字段訪問是一個慣例。如果TracingInterceptor可以過濾而不是跟蹤這些方法,那將非常好。 這個例子向你說明,如何使用JBoss AOP元數據來實現基於一個每方法結構的過濾。通常,元數據被用於更復雜的,如定義事務屬性、每方法安全角色或者持久映射,但是這個例子將足以說明元數據如何被用於一個能使用AOP的應用程序。

定義類元數據

為了添加這個過濾功能,我們將提供了一個標志,你可以使用它來關閉跟蹤。 我們將回到我們的AOP XML文件,來定義將刪除對get()和set()方法的跟蹤的標記。 實際上,跟蹤main()函數也有點意義不大,所以讓我們也把這個跟蹤給過濾掉。

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

上面的XML定義一組名為tracing的屬性。 過濾屬性將被附加到每個以get或set開頭的方法。 正則表達式格式使用JDK 1.4定義的表達式。 這個元數據可以在TracingInterceptor裡通過Invocation對象訪問。

訪問元數據

對於有用的元數據,它必須在運行時間可訪問。 類元數據可以通過Invocation對象訪問。 為了在我們的例子中使用它,必須稍微修改一下TracingInterceptor。

public class TracingInterceptor implements Interceptor
{
public String getName() { return TracingInterceptor; }
public InvocationResponse invoke(Invocation invocation)
throws Throwable
{
String filter = (String)invocation.getMetaData(tracing, filter);
if (filter != null && filter.equals(true))
return invocation.invokeNext();
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
{
// Do nothing for fields. Just too verbose.
return invocation.invokeNext();
}
System.out.println(Entering + message);
// Continue on. Invoke the real method or constructor.
InvocationResponse rsp = invocation.invokeNext();
System.out.println(Leaving + message);
return rsp;
}
}

運行例 2

POJO類已經做了一些擴展,添加了get()和set()方法。

public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
private int counter = 0;
public int getCounter() { return counter; }
public void setCounter(int val) { counter = val; }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojo.helloWorld();
pojo.setCounter(32);
System.out.println(counter is: + pojo.getCounter());
}
}

TracingInterceptor將監聽main()、POJO()和helloWorld()的調用。輸出為:

Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld

你可以在下面的網址下載JBoss AOP和示例代碼:

(http://www.jboss.org/index.html?module=html&op=userdisplay&id=developers/projects/jboss/aop)。

編譯和執行:

$ cd oreilly-aop/example2
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader POJO

例3.使用引入

如果我們可以關閉或者打開指定實例的跟蹤,那麼將會非常理想。 JBoss AOP有一個應用程序接口把元數據附加到一個對象實例中,但是讓我們假裝一個實際的跟蹤應用程序接口是最佳解決方案。 在本例中,我們將通過使用一個引入改變POJO類本身的定義。 我們將強制POJO類實現一個跟蹤接口,並且提供一個混合類來處理新的跟蹤應用程序接口。 下面是這個跟蹤接口:

public interface Tracing
{
public void enableTracing();
public void disableTracing();
}

定義一個混合類

面向形式Tracing接口將在一個混合類中實現。當一個POJO被實例化的時候,這個混合類的一個實例將被附加到這個POJO類中。下面是實現:

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 ()方法附加過濾屬性到這個對象實例上。 disableTracing ()方法做相同的事情,但是把filter屬性設置為false。 這兩個方法是元數據如何被用於做類級別以外的事情的例子。 元數據也可以應用在實例水平。

附加一個引入

好的,這樣我們就已經定義了跟蹤接口並實現了混合類。 下一步是把引用附加到POJO類。至於監聽器,我們必須在XML中定義另一個切入點。讓我們看看這個XML。

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

上面的切入點將強制POJO類來實現Tracing接口。 現在,當POJO的一個實例被實例化,TracingMixin的一個實例也將被實例化。 TracingMixin被實例化的方法在<construction>標記中被定義。 你可以在<construction>標記中放入任何一行你想放入的Java代碼。

運行例 3

POJO類又被擴展了一些,現在Tracing應用程序接口可以被訪問。TracingInterceptor還沒有改變,保持在例2中的樣子。

public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
Tracing trace = (Tracing)this;
pojo.helloWorld();
System.out.println("Turn off tracing.");
trace.disableTracing();
pojo.helloWorld();
System.out.println("Turn on tracing.");
trace.enableTracing();
pojo.helloWorld();
}
}

注意,我們可以把POJO的類型強制轉化為Tracing接口。輸出為:

Entering constructor: POJO()
Leaving constructor: POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
Turn off tracing.
Entering method: disableTracing
Leaving method: disableTracing
Hello World!
Turn on tracing.
Entering method: helloWorld
Hello World!
Leaving method: helloWorld

注意,添加到TracingInterceptor的監聽器-切入點也應用於被Tracing引入引入的方法。

編譯並且運行這個例子:

$ cd oreilly-aop/example3
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.SystemClassLoader POJO

結論

面向Aspect編程是用於軟件開發的一個功能強大的新工具。 使用JBoss 4.0,你可以實現你自己的監聽器、元數據和引入,使你的軟件開發過程更加高效。 訪問www.jboss.org得到更多的詳細技術資料。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved