面向方面的Annotation

发表于:2008-02-17来源:作者:点击数: 标签:Annotation
Annotation概述 首先让我们给出这两项技术的一个概述。Annotation是JDK5.0的新功能,它在JSR-175规范中有详细定义。它们允许您以 安全 的方法定义元数据并应用到类,方法,构造程序,字段或参数中。对于你们中熟悉XDoclet的人来说,Annotation将非常直观,您
Annotation概述

  首先让我们给出这两项技术的一个概述。Annotation是JDK5.0的新功能,它在JSR-175规范中有详细定义。它们允许您以安全的方法定义元数据并应用到类,方法,构造程序,字段或参数中。对于你们中熟悉XDoclet的人来说,Annotation将非常直观,您可以用来声明标签以产生代码。两者的主要不同是Annotation是Java语言的一部分而XDoclet标签可能会打错并且难以创建。我喜欢用例子来说明,所以让我们展示一个简单的例子。

  要定义一个Annotation,您所要做的就是声明一个特殊类型的Java接口。

  清单1: Orange.java
clearcase/" target="_blank" >cccccc>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的成员方法被调用,该对象实例的锁监视器将被保持直到目前的事务提交或回退。控制该行为的方面也将做死锁检测并在发生死锁时抛出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语法。基本上,以安全的方式扩展Java语言的能力已尽在掌握。

  资源
  ·Matrix-Java开发者社区: http://www.matrix.org.cn
  ·onjava.com: onjava.com

原文转自:http://www.ltesting.net