面向方面的Annotation作者:Bill Burke,Enterprise JavaBeans 第四版合著者 譯者:yahveyeye版權聲明:任何獲得Matrix授權的網站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明作者:Bill Burke;
yahveyeye原文地址:
http://www.onjava.com/pub/a/onjava/2004/08/25/aoa.html中文地址:
http://www.matrix.org.cn/resource/article/44/44052_Annotation+Aop.html關鍵詞: Annotation Aop
Annotation
是J2SE5.0的一項新功能,它允許您附加元數據到Java構建中。同時,面向方面編程(AOP)是一個相當新的技術,它可以使您封裝某些行為,這些行
為是在使用面向對象(OO)技術時會更為混亂,困難甚至是不可能完成。這兩項技術結合起來給框架開發者開發的APIs更好的表達方式。本文深入結合這些技
術,使用Jboss AOP框架,以不同的代碼范例向您展示如何結合兩者來實際地擴展Java 語言。
相關文章:Java Annotation入門:
http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html Annotation概述首
先讓我們給出這兩項技術的一個概述。Annotation是JDK5.0的新功能,它在JSR-175規范中有詳細定義。它們允許您以安全的方法定義元數
據并應用到類,方法,構造程序,字段或參數中。對于你們中熟悉XDoclet的人來說,Annotation將非常直觀,您可以用來聲明標簽以產生代碼。
兩者的主要不同是Annotation是Java語言的一部分而XDoclet標簽可能會打錯并且難以創建。我喜歡用例子來說明,所以讓我們展示一個簡單
的例子。
要定義一個Annotation,您所要做的就是聲明一個特殊類型的Java接口。
清單1:Orange.java
package org.jboss.collors;
public @interface Orange{}
定義了這個接口,您就可以用來提供更多的描述給您的Java元素。
清單2:Foo.java
package org.jboss.examples;
public class Foo
{
@Orange void someMethod();
@Orange private int someField;
}
那么我們可以用Annotation來干什么呢?一些人想用Annotation來產生代碼并替代XDoclet,其他人,象J2EE和EJB3.0專家組,將它視為部署描述符的替代。本文談論在AOP中如何使用Annotation
AOP概述有許多的文章和書籍解釋AOP到底是什么,例如Graham O'Regan的ONJava文章“Introduction to Aspect-Oriented Programming."我將在本文給出一個快速的概覽,但我鼓勵您在線做更多的研究。
假設您要添加代碼到一個應用程序去測試調用一個特定的java方法所需的總的時間。該代碼可能看起來如下:
清單3:public class BankAccount
{
public void withdraw(double amount)
{
long startTime = System.currentTimeMillis();
try
{
// Actual method body...
}
finally
{
long endTime = System.currentTimeMillis() - startTime;
System.out.println("withdraw took: " + endTime);
}
}
}
雖然這些代碼能夠正常工作,但這個方法有一些問題:
1.它難以打開和關閉測試,您必須在try/finally塊中對每個方法或購置函數手工增加代碼以進行基準測試。
2。這一輪廓代碼并不真正屬于貫穿整個應用的代碼。它使得您的代碼臃腫并難以理解,因為您必須將計時放在try/finally塊中。
3、如果您想擴展它的功能以包含一個方法或是失敗計數,或甚至是注冊這些統計數據到一個更為復雜的報告機制中,您必須修改大量不同文件(又一次)。
Metrics
類提供了一個什么是橫切(cross-cutting)關系的完美,簡潔的小例子。Jboss
AOP以一種含蓄的方式提供了一個簡單的方法來封裝和應用這樣的關系,這樣某些象度量操作代碼不會弄亂您的編碼。讓我們稍為深入到Jboss
AOP一些來看看如何實現。
為了使用Jboss AOP封裝度量功能,您首先需要定義一個方面來指出該度量行為。
清單4:public class Metrics
{
public Object profile(MethodInvocation invocation) throws Throwable
{
long startTime = System.currentTimeMillis();
try
{
return invocation.invokeNext();
}
finally
{
long endTime = System.currentTimeMillis() - startTime;
java.lang.reflect.Method m = invocation.getMethod();
System.out.println("method " + m.toString() +
" time: " + endTime + "ms");
} }
}
一
個方面只是一個具有定義了您想要附加到您的對象模型的行為的普通Java類。這些方法的簽名必須返回一個java.lang.Object并且必須具有一
個(并且只有一個)Jboss AOP
調用對象參數,它被用來封裝方法,構造函數或字段調用。方法名可以是任何你想要的并且當您綁定該方面到您的代碼片斷時被引用。
下面要做的事情就是實際應用方面到您想要它勾勒一個方法的執行的某個程序點。大多數AOP框架提供了一個指向表達式語言,在此處您可以定義您想要某個方面行為被附加到的位置。下面是在Jboss AOP中的做法。
清單5:jboss-aop.xml
<aop>
<aspect class="Metrics"/>
<bind pointcut="execution(public void BankAccount->withdraw(double amount))">
<advice name="profile" aspect="Metrics"/>
</bind>
</aop>
采用在Metrics.java中對方面的定義和jboss-aop.xml中的指向定義,該度量代碼現在以含蓄而又透明地應用到BankAccount.withdraw()方法中并能在勾勒代碼不再需要時輕易地移除。
對于Jboss AOP更多的信息,請查詢分發包中的指南。其中具有大約20個例子來帶領您漫游如何使用Jboss AOP框架。
噓!現在我們已經進行了一個概覽,讓我們深入到本文的中心內容。我將再次給您提供一些例子,因為這是我所知道的講授一個新的概念的最好的方法。
正如我前面說的,Annotation加上AOP幾乎是給予您擴展Java語言的能力。Annotation提供了聲明新的,可兼容的,類型安全的語法機制。AOP提供了封裝和應用新的行為到一個語法表達式的機制。
方法Annotation和AOP讓
我們看看如何使用方法Annotation和AOP。使用Annotation和AOP并應用到一個方法類似于使用Java的synchronized關
鍵字。當您設定一個方法為synchronized,您在告訴JVM:您想該方法在被調用時以一種特殊的方式進行。Annotation允許您定義一個新
的關鍵字來觸發您自己的特殊的定制行為。AOP給予您封裝這一行為的能力并將其“編織”進該方法的執行中。再次的,這一概念的最佳描述是通過一個例子。
讓我們假設我們想要添加新的語法,使用該語法使得我們可以在方法被標簽為@Oneway時,在后臺以另一個線程調用這個void方法。可以象這樣使用新的語法:
清單6:Import org.jboss.aspects.Oneway;
public class Foo
{
@Oneway public static void someMethord(){…}
public static void main(String[] args){
somMethod();//executes in
backgroud
}
}
當someMethod()在main中被調用,它將異步運行,這樣main中的代碼可以并行執行其他任務。
要實現這一功能,首先要在一個Annotation中為我們的@Oneway標簽定義新的Java語法.
清單7:Oneway.java
package org.jboss.aspects;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
public @interface Oneway {}
夠簡單的。@Target標簽允許您縮小Annotation可以應用的地方。在本例中,我們的@OnewayAnnotation只能應用到一個方法。記住,這些都是J2SE5.0百分之百可用的純Java。
下面要做的事是定義一個封裝我們的@Oneway行為的方面類。
清單8:OnewayAspect.java
package org.jboss.aspects;
public OnewayAspect
{
private static class Task implements Runnable
{
private MethodInvocation invocation;
public Task(MethodInvocation invocation)
{
this.invocation = invocation;
}
public void run()
{
try { invocation.invokeNext(); }
catch (Throwable ignore) { }
}
}
public Object oneway(MethodInvocation invocation) throws Throwable
{
MethodInvocation copy = invocation.copy();
Thread t = new Thread(new Task(copy));
t.setDaemon(false);
t.start();
return null;
}
}
這
個方面夠簡單。oneway()方法拷貝invocation,創建一個線程,在后臺啟動整個調用并返回。我們可以想象一個更為復雜的例子:使用J2SE
5.0 java.util.concurrent包中的某些新的Executors,但這些代碼很有希望闡明了如何基于這個例子構建更為復雜的實現。
最后必須要做的事情是指定當@OnewayAnnotation在一個方法中聲明時觸發OnewayAspect應用的指向表達式。
清單9:jboss-aop.xml
<aop>
<aspect class="org.jboss.aspects.OnewayAspect"/>
<bind pointcut="execution(void *->@org.jboss.Oneway(..))">
<advice name="oneway"
aspect="org.jboss.aspects.OnewayAspect"/>
</bind>
</aop>
該
指向表達式規定任何具有@Oneway標簽的void方法都應該有OnewayAspect.oneway()方法在它本身執行前被執行。隨著
Annotation,方面和現在定義的指向表達式,@Oneway語法現在可以用于您的應用程序中。一個簡單,清晰,易于實現的方法來擴展Java
語言!
字段Annotation和AOP讓
我們看看如何使用字段Annotation和AOP。使用Annotation和AOP,您可以改變一個對象的字段或是作為一個類的靜態成員的實際存儲方
式。在這個例子里我們要完成的是當您將一個字段(靜態或是成員)標記上@ThreadBased,盡管是將它存儲在
java.lang.ThreadLocal,但它的值依然正常。當然,您可以直接使用ThreadLocal變量,但問題是ThreadLocal并非
一個類型并且您必須使用“麻煩的”(好,它們并沒有那么羅嗦)get()和set()方法。那么我們現在做的就是創建一個ThreadLocal類型的字
段。我們主要的將創建一個稱為@Thradbased變量的新的Java字段類型。
象這樣使用新的類型:
清單10:import org.jboss.aspects.Threadbased;
public class Foo
{
@Threadbased private int counter;
}
為了實現這個功能,我們必須先定義Annotation
清單11:Threadbased.java
package org.jboss.aspects;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
public @interface Threadbased {}
夠簡單。@Target標簽允許您縮小Annotation可以應用的地方。在本例中,我們的@ThreadbasedAnnotation只能應用到字段。
下面的事情是定義封裝我們的ThreadLocal行為的方面。
清單12:ThreadbasedAspect.java
package org.jboss.aspects;
import org.jboss.aop.joinpoint.*;
import java.lang.reflect.Field;
public class ThreadbasedAspect
{
private ThreadLocal threadbased = new ThreadLocal();
public Object access(FieldReadInvocation invocation)
throws Throwable
{
// just in case we have a primitive,
// we can't return null
if (threadbased.get() == null)
return invocation.invokeNext();
return threadbased.get();
}
public Object access(FieldWriteInvocation invocation)
throws Throwable
{
threadbased.set(invocation.getValue());
return null;
}
}
ThreadbasedAspect
封裝到一個Java字段的訪問。它里面具有一個專門的ThreadLocal變量跟蹤thradlocal變為一個特殊的字段。它還有一個單獨的
access()方法,該方法根據一個字段的get或set方法是否被調用決定它是否被調用。這些方法委托給ThreadLocal來獲得字段的當前值。
最后,我們必須定義一個指向表達式,當@ThreadbasedAnnotation在某個字段被指定時觸發ThreadbasedAspect的應用。
清單13:jboss-aop.xml
<aop>
<aspect class="org.jboss.aspects.ThreadbasedAspect" scope="PER_JOINPOINT"/>
<bind pointcut="field(* *->@org.jboss.aspects.Threadbased)">
<advice name="access"
aspect="org.jboss.aspects.ThreadbasedAspect"/>
</bind>
</aop>
只
有當我們具有多個@Threadbased變量定義在同一個類時,我們需要為每個靜態字段分配一個ThreadbasedAspect實例。對于成員變
量,我們需要為每個字段,每個對象實例分配一個ThreadbasedAspect實例。為了促進這一行為,方面定義通過設定實例為
PER_JOINPOINT限制方面類的實例何時和何地被分配出去的范圍。如果我們不做限制,Jboss
AOP會只分配一個ThreadbasedAspect實例并且不同的字段會共享相同的ThreadLocal接口——這不是我們所希望的。
好就這樣。一個清晰容易的擴展Java來指定一個新的特殊類型的方法。注意:該特殊的方法來自Jboss AOP束。
依賴注入字
段Annotation和AOP可以使用的一個有趣的地方是依賴注入。依賴注入是關于對象聲明它們需要什么信息,配置或服務引用以及運行時自動注入這些依
賴而不是用代碼明確地在一個注冊中心查找。在J2EE領域,獲得javax.transaction.TransactionManager服務的訪問并
未標準化并且實際上不同的廠商有不同的實現。許多框架開發者需要使用TransactionManager來實現定制事務服務。使用字段
AnnotationAOP提供依賴注入并抽取出一個需要TransactionManager的組件如何引用它的細節是一個了不起的方法。讓我們定義一
個方面,它將注入一個TransactionManager引用到一個字段值中。
首先,我們再次定義我們的Annotation。
清單14:Inject.java
package org.jboss.aspects;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
public @interface Inject {}
下面我們將定義方面類,它封裝了TransactionManager的解析。該方面是特定于JBoss應用服務器,但您可以定義為每個廠商定義不同的實現。
清單15:InjectTMAspect.java
package org.jboss.aspects;
import org.jboss.aop.joinpoint.*;
import java.lang.reflect.Field;
import javax.transaction.TransactionManager;
import org.jboss.tm.TxManager;
public InjectTMAspect
{
private TransactionManager tm = TxManager.getInstance();
public Object access(FieldReadInvocation invocation)
throws Throwable {
return tm;
}
public Object access(FieldWriteInvocation invocation)
throws Throwable {
throw new RuntimeException(
"Setting an @Injected variable is illegal");
}
}
最后,我們必須定義XML綁定來觸發當@Inject標簽應用到一個字段時InjectTMAspect的應用。指向表達式基本上說明了對任意一個標記為@Inject的TransactionManager字段應用InjectTMAspect。
清單16:<aop>
<aspect class="org.jboss.aspects.InjectTMAspect"/>
<bind pointcut="field(javax.transaction.TransactionManager *->@org.jboss.aspects.Inject)">
<advice name="access"
aspect="org.jboss.aspects.InjectTMAspect"/>
</bind>
</aop>
現在Annotation、方面類和XML綁定已經定義,我們可以在我們的代碼中使用了。
清單17:import javax.transaction.TransactionManager;
import org.jboss.aspects.Inject;
public class MyTransactionalCache
{
@Inject private TransactionManager tm;
...
}
更多預打包例子Jboss
AOP不僅僅是關于AOP框架。它還有一個豐富的方面庫,您可以直接在您的應用中使用。在這個庫中是一個比我們現在在本文展示的例子更為復雜的
Annotation方面集。這些方面包括異步調用,事務劃分,事務鎖定和基于角色的安全。讓我們簡要地瀏覽一下以提供給您一個更好的關于
Annotation和AOP共同工作的考慮。
異步方面Jboss
AOP異步方面允許您定義任何方法為異步的,這樣它可以在后臺被執行。這對于我們的@Oneway例子來說有些困難,因為它使用Oswego并行包中的執
行器工具,并為那些具有一個返回類型的方法提供了一個方法來異步地接收回響應。要使用這個方面,您只需標記一個方法為@Asybchronous.
清單18:public Foo {
@Asynchronous public int someMethod(int someArg) {...}
}
@Asynchronous
標簽的應用做了一些事情。與在本文中的@Oneway例子一樣,它應用一個在后臺運行該方法的方面。而且,采用@Asynchronous標簽,您并不僅
限于void方法并可于實際上返回一個值的方法進行交互。當@Asynchronous標簽被應用,它強制Foo類實現
AsynchronousFacade接口。在AOP領域,這稱為接口引入(interface
introduction)。AsynchronousFacade接口允許您預測一個響應或以超時限定等待一個響應。最好用一個例子來解釋。
清單19:Foo foo = new Foo();
someMethod(555); // executes in background
AsynchronousFacade facade = (AsynchronousFacade)foo;
AsynchronousResponse response = facde.waitForResponse();
System.out.println(response.getReturnValue());
您可以啟動多個不同對象的多個不同方法的多個調用,并異步積累它們的響應。
事務鎖定有時在J2EE事務期間而不是一個方法執行,構造函數調用或同步塊執行期間同步一個對象或類會很有用。對這類事務同步或鎖定,Jboss AOP發明了@TxSynchronized關鍵字。您可以使用@TxSynchronized在任意成員或靜態方法已經構造函數上。
清單20:import org.jboss.aspects.txlock.TxSynchronized;
public FooBar
{
@TxSynchronized public FooBar() {}
@TxSynchronized static void staticMethod() {}
@TxSynchronized public memberMethod() {}
}
如
果一個被標記為@TxSynchronized的構造函數或靜態方法被調用,類的鎖監視器會在事務執行期間被保持著。如果一個標記為
@TxSynchronized的成員方法被調用,該對象實例的鎖監視器將被保持直到目前的事務提交或回退??刂圃撔袨榈姆矫嬉矊⒆鏊梨i檢測并在發生死鎖
時拋出RuntimeException。
J2EE 散餐(原文法文:a la carte)之:事務劃分EJB3.0已經定義了一些Annotation進行事務劃分。Jboss AOP在此基礎上構建。這樣您可以通過指定Annotation應用事務劃分到任意方法(靜態或成員)以及任何Java類構造函數。
清單21:import org.jboss.aspects.tx.*;
public class Foo
{
@Tx(TxType.REQUIRED) public Foo {}
@Tx(TxType.REQUIRESNEW) public static createFoo() {
return new Foo();
}
}
J2EE 散餐之:基于角色的安全EJB 3.0也定義了一些Annotation實現基于角色的安全。Jbos AOP是基于此構建的,所以您可以應用基于角色的安全到任何的字段或方法(靜態或成員)已經構造函數。
清單22:import org.jboss.aspects.security.*;
@SecurityDomain("LDAP Repository")
public class Foo
{
@Permissions({"admin"}) public Foo() {}
@Permissions({"developer"}) public static int status;
@Permissions({"anybody"}) public static void doSomething() {...}
}
EJB演變 隨
著AOP與EJB規范一起漸漸成熟,我真正希望發生的是EJB規范定義的Annotation將能在任何上下文作為新的Java語言的形容詞被使用,而不
是讓它們有限的使用在會話bean中。想象一下,一個真正的無狀態bean僅僅成為一個明文Java類的一個靜態方法集。
清單23:public MySessionBean
{
@Tx(TxType.REQUIRED) public static doSomething() {...}
}
無論如何,這些關于AOP和EJB的討論很可能就是為了EJB4.0。
結論并非是限制J2SE5.0Annotation用于代碼生成,Annotation和AOP可以被結合起來提供新的能力給框架開發者。這一結合允許開發者定義新的具有行為附加到其上的Java語法?;旧希园踩姆绞綌U展Java語言的能力已盡在掌握。
資源 ·Matrix-Java開發者社區:
http://www.matrix.org.cn·onjava.com:
onjava.com關于作者Bill Burke :JBoss 首席架構師.