面向方面编程(AOP)和Jboss

发表于:2008-02-03来源:作者:点击数: 标签:
概述 面向方面编程(Aspect-Oriented Programming, AOP)是一个令人兴奋的新模式。就 开发 软件系统而言,它的影响力将会和有15到20年的 面向对象 一样。面向方面编程和面向对象编程不但不是互相竞争的技术而且是可以很好的互补。面向对象编程主要用于为同一
概述

  面向方面编程(Aspect-Oriented Programming, AOP)是一个令人兴奋的新模式。就开发软件系统而言,它的影响力将会和有15到20年的面向对象一样。面向方面编程和面向对象编程不但不是互相竞争的技术而且是可以很好的互补。面向对象编程主要用于为同一对象层次的公用行为建模。它的弱点是将公共行为应用于多个无关对象模型之间。而这恰恰是AOP适合的地方。AOP允许定义交叉的关系,那些关系应用于跨国分开的,非常不同的对象模型。AOP允许你层次化功能性而不是嵌入功能性,那使得代码有更好的可度性和易于维护性。我喜欢认为OOP是自上而下的软件开发,而AOP是自左而右的软件开发,它们是完全直交的技术,并且互相很好的补充。

  在OOP的工具里是继承,封装和多态,而AOP的组件是通知/拦截器,导言,元数据和pintcuts.让我们看一下这些定义。

  通知/拦截器

  一个通知是一个逻辑,这个逻辑有特定的事件触发。它是行为,这个行为能够被插入在调用者和被调用者之间,在一个方法调用者和实际的方法之间。通知是AOP真正的关键。通知允许你去透明的应用一些事物,像日志和记录到一个存在的对象模型。

  在 JBoss AOP中,我们用拦截器是实现了通知。你能够定义拦截器,它拦截方法调用,构造器调用和域访问。后面,我们将阐明怎样应用这些拦截器到一个存在的对象模型。

  导言

  导言是一个增加方法或者域到一个存在的类中的途径。它们甚至允许你改变当前存在的类是显的接口,并且引入一个混合的类,这个类是实现了新的接口。导言允许你带入多继承到一般的Java类。导言一个主要的用例是当你有一个方面,你想让这个方面有一个运行时间借口时。你想应用你的方面跨越不同的对象层次,但是你仍然要应用开发者去能够调用特定方面的APIs.

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

  导言能够是一个方法,它将一个新的API绑定到一个存在的对象模型。

  元数据

  元数据是能够绑定到一个类的附加信息,在静态或者运行时间。元数据更加有力力量的是,你能够动态绑定元数据到一个给定的对象实例。元数据非常强大的,当你真正编写应用于任何对象的一般方面,而逻辑需要知道制定类的信息时。在使用的一个好的元数据类比就是EJB规范。在EJB的XML发布描述符中,你需要定义基于每一个方法的事务属性。应用服务器指导什么时候,什么地方开始,挂起或者提交一个事务,因为你在BEAN的XML的配置文件中的元数据内已经定义如方法:Required,RequiresNew,Support等等,它们绑定在你的EJB类和事务管理之间。

  C#把元数据成为了这个语言的组成部分。XDoclet是另一个动作的元数据的例子。如果你曾经用过XDoclet生成过EJB文件和发布描述符,你就会知道元数据的力量。在JDK1.5中,当元数据被加入java语言中,JCP一致同意。(见JSR175)。尽管直到JSR175成为了事实,一个好的AOP框架也应该提供一种机制去定义在运行时间有效的类级元数据。
Pointcuts

  如果拦截器,导言和元数据是AOP的特征,那么pointcuts就是粘合剂。Pointcuts告诉AOP框架,那些拦截器绑定到那些类, 什么原数据将应用于那些类或者那一个导言将被传入那些类。Pointcuts定义各种AOP特征将怎样应用于你应用中的类。

在动作中的AOP

  例1.使用拦截器

  JBoss 4.0带了一个AOP框架。这个框架和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中,被拦截的所有域,构造器和方法被转成一般的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
{
// 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, pointcuts 是在一个XML文件中定义的。让我们看一下这看起来象什么。

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

  上面的pointcut绑定TracingInterceptor到一个叫做POJO的类。这看起来有一点麻烦;我们不得不为每一个想跟踪的类创建一个pointcut吗?幸运的是,interceptor-pointcut的类属性可以用任何的正规表达式。所以如果你想跟踪由JVM载入的类,类表达式将变为 .*。如果你仅仅想跟踪一个特定的包,那么表达式将是com.acme.mypackge.*。

  当单独运行JBoss AOP时,任何符合 META-INF/jboss-aop.xml模式的XML文件将被JBoss AOP 运行时间所载入。如果相关的路径被包含在任何JAR或你的CLASSPATH的目录中,那个特定的XML文件将在启动时,由JBoss AOP 运行时间所载入。

  运行这个例子

  我们将用上面定义的pointcut去运行例子。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

  你能够在这里下载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制定的一个类载入器覆盖系统的类载入器。

  TraceingInterceptor不跟踪域访问,因为它有一点繁琐。对于开发者,实现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定义的表达式。元数据通过Invocation对象,在TracingInterceptor内访问。

  访问Metadata

  为了用元数据,它在运行时间必须是可达的。类的元数据是通过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和离子代码。编译和执行: $ 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有一个API,他绑定元数据到一个对象实例,但是让我们伪装一个实际的跟踪API是一个更好的方案。在这例子中,我们通过用一个导言,将改变POJO类的本身的定义。我们将强制POJO类去实现一个跟踪借口和提供混合类,这个混合类处理新的跟踪API。这将是跟踪借口:

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()方法绑定filter属性到对象实例。在disableTracing()方法作同样的事,但是制定filter属性为false。这两个方法是元数据能够怎么样用于超过一个类级别。元数据也能够实例级的应用。元数据应用在实例级别。

  绑定一个导言

  好了,所以我们定义跟踪接口,并且实现这个混合类。下一步是应用导言到POJO类。像拦截器,我们必须在XML中定义一个ponitcut。让我们看一下这项什么。

<?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>

  上面的pointcuts将强制POJO类实现Tracing接口。现在,当一个POJO实例被初始化,一个TracingMixin也将被实例化。TracingMixin被初始化的途径被定义在标签中。你能够把想要的任一行Java代码放入在标签中。

运行例子3

  POJO类为了显示TracingAPI怎么被访问,它已经被扩展了一点。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 中的interceptor-pointcut也应用到那些通过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

  结论:面向方面编程对于软件开发是一个强有力的新工具。为了使你的软件开发过程更加动态和流畅,用JBoss4.0,你能够实现你自己的拦截器,元数据和导言。更详细的文档参见站点www.jboss.org。

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