Java面向方面编程概述
副标题#e#
概述
面向方面的措施设计(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框架才气提供一个机制,声明在运行时间有效的类级元数据。
切入点
假如监听器,引入和元数据是面向方面的措施设计的特性,那么切入点就是把这些特性接洽起来的纽带。切入点汇报面向方面的措施设计框架,哪个监听器将和哪个类绑缚在一起,哪些元数据将用于哪些类,可能引入将被导入到哪些类中。 切入点界说可以或许用于你的应用措施的类的各类面向方面的措施设计特性。
#p#副标题#e#
事情中的面向方面的措施设计
例子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工具还驱动监听器链。为了表明清楚,我们来看看在一个例程中所有这些工具如何利用。
#p#分页标题#e#
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()函数也有点意义不大,所以让我们也把这个跟踪给过滤掉。
#p#分页标题#e#
<?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。 这两个要领是元数据如何被用于做类级别以外的工作的例子。 元数据也可以应用在实例程度。
附加一个引入
#p#分页标题#e#
好的,这样我们就已经界说了跟踪接口并实现了殽杂类。 下一步是把引用附加到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获得更多的具体技能资料。