概述
面向方面编程(AspectOriented Programming AOP)是一个令人兴奋的新模式就开发软件系统而言它的影响力将会和有到年的面向对象一样面向方面编程和面向对象编程不但不是互相竞争的技术而且是可以很好的互补面向对象编程主要用于为同一对象层次的公用行为建模它的弱点是将公共行为应用于多个无关对象模型之间而这恰恰是AOP适合的地方AOP允许定义交叉的关系那些关系应用于跨国分开的非常不同的对象模型AOP允许你层次化功能性而不是嵌入功能性那使得代码有更好的可度性和易于维护性我喜欢认为OOP是自上而下的软件开发而AOP是自左而右的软件开发它们是完全直交的技术并且互相很好的补充
在OOP的工具里是继承封装和多态而AOP的组件是通知/拦截器导言元数据和pintcuts让我们看一下这些定义
通知/拦截器
一个通知是一个逻辑这个逻辑有特定的事件触发它是行为这个行为能够被插入在调用者和被调用者之间在一个方法调用者和实际的方法之间通知是AOP真正的关键通知允许你去透明的应用一些事物像日志和记录到一个存在的对象模型
在 JBoss AOP中我们用拦截器是实现了通知你能够定义拦截器它拦截方法调用构造器调用和域访问后面我们将阐明怎样应用这些拦截器到一个存在的对象模型
导言
导言是一个增加方法或者域到一个存在的类中的途径它们甚至允许你改变当前存在的类是显的接口并且引入一个混合的类这个类是实现了新的接口导言允许你带入多继承到一般的Java类导言一个主要的用例是当你有一个方面你想让这个方面有一个运行时间借口时你想应用你的方面跨越不同的对象层次但是你仍然要应用开发者去能够调用特定方面的APIs
Apple apple = new Apple();
LoggingAPI logging = (LoggingAPI)apple;
ApplesetLoggingLevel(VERBOSE);
导言能够是一个方法它将一个新的API绑定到一个存在的对象模型
元数据
元数据是能够绑定到一个类的附加信息在静态或者运行时间元数据更加有力力量的是你能够动态绑定元数据到一个给定的对象实例元数据非常强大的当你真正编写应用于任何对象的一般方面而逻辑需要知道制定类的信息时在使用的一个好的元数据类比就是EJB规范在EJB的XML发布描述符中你需要定义基于每一个方法的事务属性应用服务器指导什么时候什么地方开始挂起或者提交一个事务因为你在BEAN的XML的配置文件中的元数据内已经定义如方法RequiredRequiresNewSupport等等它们绑定在你的EJB类和事务管理之间
C#把元数据成为了这个语言的组成部分XDoclet是另一个动作的元数据的例子如果你曾经用过XDoclet生成过EJB文件和发布描述符你就会知道元数据的力量在JDK中当元数据被加入java语言中JCP一致同意(见JSR)尽管直到JSR成为了事实一个好的AOP框架也应该提供一种机制去定义在运行时间有效的类级元数据
Pointcuts
如果拦截器导言和元数据是AOP的特征那么pointcuts就是粘合剂Pointcuts告诉AOP框架那些拦截器绑定到那些类 什么原数据将应用于那些类或者那一个导言将被传入那些类Pointcuts定义各种AOP特征将怎样应用于你应用中的类
在动作中的AOP
例.使用拦截器
JBoss 带了一个AOP框架这个框架和JBoss应用服务器紧密地结合但是你也能够在你的应用中单独的运行它直到你看了动作中看到它你才会完全的理解这个概念所以让我们用一个来自于JBoss AOP的例子来说明这个模块所有的部分是如何一起工作的在这章余下的部分我们将建立一个例子来跟蹤使用AOP的框架
定义一个拦截器
为了实现我们对于框架的跟蹤我们必须作的第一件事是定义一个拦截器它将作实际的工作在JBOSS AOP中所有的拦截器必须实现orgjbossaopInterceptor 接口
public interface Interceptor
{
public String getName();
public InvocationResponse invoke(Invocation invocation)
throws Throwable;
}
在JBossAOP中被拦截的所有域构造器和方法被转成一般的invoke调用方法的参数被填入一个Invocation对象并且方法的返回值域的存取或者构造器被填入一个InvocationResponse对象这个Invocation对象也驱动这个拦截链为了清楚地说明这个让我们看一下在这个例子中所有的对象是如何配合到一起的
import orgjbossaop*;
import javalangreflect*;
public class TracingInterceptor implements Interceptor
{
public String getName() { return TracingInterceptor; }
public InvocationResponse invoke(Invocation invocation)
throws Throwable
{
String message = null;
if (invocationgetType() == InvocationTypeMETHOD)
{
Method method = MethodInvocationgetMethod(invocation);
message = method: + methodgetName();
}
else if (invocationgetType() == InvocationTypeCONSTRUCTOR)
{
Constructor c = ConstructorInvocation
getConstructor(invocation);
message = constructor: + ctoString();
}
else
{
// Do nothing for fields Just too verbose
//对于域什么也不做太繁琐
return invocationinvokeNext();
}
Systemoutprintln(Entering + message);
// Continue on Invoke the real method or constructor
// 继续调用真正的方法或者构造器
InvocationResponse rsp = invocationinvokeNext();
Systemoutprintln(Leaving + message);
return rsp;
}
}
上面的拦截器将拦截所有的对一个域构造器或方法的调用如果调用的类型是一个方法或者构造器一个带有方法或构造器签名的消息将输出到控制平台
绑定拦截器
好了这样我们就定义了拦截器但是怎么绑定这个拦截器到实际的类?为了做这个我们需要定义一个pointcut对于JBoss AOP pointcuts 是在一个XML文件中定义的让我们看一下这看起来象什么
<?xml version= encoding=UTF>
<aop>
<interceptorpointcut class=POJO>
<interceptors>
<interceptor class=TracingInterceptor />
</interceptors>
</interceptorpointcut>
</aop>
上面的pointcut绑定TracingInterceptor到一个叫做POJO的类这看起来有一点麻烦我们不得不为每一个想跟蹤的类创建一个pointcut吗?幸运的是interceptorpointcut的类属性可以用任何的正规表达式所以如果你想跟蹤由JVM载入的类类表达式将变为 *如果你仅仅想跟蹤一个特定的包那么表达式将是comacmemypackge*
当单独运行JBoss AOP时任何符合 METAINF/jbossaopxml模式的XML文件将被JBoss AOP 运行时间所载入如果相关的路径被包含在任何JAR或你的CLASSPATH的目录中那个特定的XML文件将在启动时由JBoss AOP 运行时间所载入
运行这个例子
我们将用上面定义的pointcut去运行例子POJO类看起来如下
public class POJO
{
public POJO() {}
public void helloWorld() { Systemoutprintln(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojohelloWorld();
}
}
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 oreillyaop/example
$ export CLASSPATH=;jbosscommonjar;jbossaopjar;javassistjar
$ javac *java
$ java Djavasystemclassloader=orgjbossaopstandalone
SystemClassLoader POJO
JBoss AOP 对绑定的拦截器做字节码操作因为没有编译步骤AOP运行时间必须有ClassLoader的总控如果你正运行在非JBoss应用服务器你必须用JBoss制定的一个类载入器覆盖系统的类载入器
TraceingInterceptor不跟蹤域访问因为它有一点繁琐对于开发者实现get()和set()方法去封装域访问是一个一般的实践如果TracingInterceptor能够过滤出并且不跟蹤这些方法那是非常好的这个例子显示你能够用JBoss AOP 元数据去实现基于任一方法的过滤一般元数据用于更复杂的事情如定义事务属性每个方法的安全角色或者持久性映射但是这个例子应该足够说明元数据能够怎样用在 AOP使能的应用中
定义类的元数据
为了增加这个过滤功能我们将提供一个标志你能够用这个标着去关闭跟蹤我们将回到我们的AOP的XML文件去定义标签那将删除对get()和set()方法的跟蹤事实上对于main()函数的跟蹤毫无意义所以我们也过滤出它
<?xml version= encoding=UTF>
<aop>
<classmetadata group=tracing class=POJO>
<method name=(get*)|(set*)>
<filter>true</filter>
</method>
<method name=main>
<filter>true</filter>
</method>
</classmetadata>
</aop>
上面的XML定义了一组叫做tracing的属性这个过滤属性将绑定到每一个以get或者set开始的方法上正则表达式格式用JDK定义的表达式元数据通过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)invocationgetMetaData(tracing filter);
if (filter != null && filterequals(true))
return invocationinvokeNext();
String message = null;
if (invocationgetType() == InvocationTypeMETHOD)
{
Method method = MethodInvocationgetMethod(invocation);
message = method: + methodgetName();
}
else if (invocationgetType() == InvocationTypeCONSTRUCTOR)
{
Constructor c = ConstructorInvocation
getConstructor(invocation);
message = constructor: + ctoString();
}
else
{
// Do nothing for fields Just too verbose
return invocationinvokeNext();
}
Systemoutprintln(Entering + message);
// Continue on Invoke the real method or constructor
InvocationResponse rsp = invocationinvokeNext();
Systemoutprintln(Leaving + message);
return rsp;
}
}
运行例子: POJO类将扩展一点增加get()和set()方法
public class POJO
{
public POJO() {}
public void helloWorld() { Systemoutprintln(Hello World!); }
private int counter = ;
public int getCounter() { return counter; }
public void setCounter(int val) { counter = val; }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojohelloWorld();
pojosetCounter();
Systemoutprintln(counter is: + pojogetCounter());
}
}
TracingInterceptor将拦截对main()POJO()和helloWorld()调用输出应该看起来如下
Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
你能够在这里下载JBoss AOP和离子代码编译和执行: $ cd oreillyaop/example
$ export CLASSPATH=;jbosscommonjar;jbossaopjar;javassistjar
$ javac *java
$ java Djavasystemclassloader=orgjbossaopstandalone
SystemClassLoader POJO
例子使用导言
如果我们能够为特定的实例关闭和打开那将很酷JBoss AOP有一个API他绑定元数据到一个对象实例但是让我们伪装一个实际的跟蹤API是一个更好的方案在这例子中我们通过用一个导言将改变POJO类的本身的定义我们将强制POJO类去实现一个跟蹤借口和提供混合类这个混合类处理新的跟蹤API这将是跟蹤借口:
public interface Tracing
{
public void enableTracing();
public void disableTracing();
}
定义一个混合的类
Tracing接口将在混合类中实现当一个POJO是实例时一个混合对象混合类将绑定到POJO类下面是实现:
import orgjbossaopAdvised;
public class TracingMixin implements Tracing
{
Advised advised;
Public TracingMixin(Object obj)
{
thisadvised = (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= encoding=UTF>
<aop>
<introductionpointcut class=POJO>
<mixin>
<interfaces>Tracing</interfaces>
<class>TracingMixin</class>
<construction>new TracingMixin(this)</construction>
</mixin>
</introductionpointcut>
</aop>
上面的pointcuts将强制POJO类实现Tracing接口现在当一个POJO实例被初始化一个TracingMixin也将被实例化TracingMixin被初始化的途径被定义在标签中你能够把想要的任一行Java代码放入在标签中
运行例子
POJO类为了显示TracingAPI怎么被访问它已经被扩展了一点TracingInterceptor仍然和例子一样
public class POJO
{
public POJO() {}
public void helloWorld() { Systemoutprintln(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
Tracing trace = (Tracing)this;
pojohelloWorld();
Systemoutprintln(Turn off tracing);
tracedisableTracing();
pojohelloWorld();
Systemoutprintln(Turn on tracing);
traceenableTracing();
pojohelloWorld();
}
}
注意我们转换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 中的interceptorpointcut也应用到那些通过Tracing 导言导入的方法中为了编译和运行这个例子 $ cd oreillyaop/example
$ export CLASSPATH=;jbosscommonjar;jbossaopjar;javassistjar
$ javac *java
$ java Djavasystemclassloader=orgjbossaopstandalone
SystemClassLoader POJO
结论面向方面编程对于软件开发是一个强有力的新工具为了使你的软件开发过程更加动态和流畅用JBoss你能够实现你自己的拦截器元数据和导言更详细的文档参见站点wwwjbossorg