| |
概述
面向方面的程序设计(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得到更多的详细技术资料。
|
|