面向方面编程

背景

 

AOP(面向方面编程,是Aspect Oriented Programming的缩写) 是上世纪 90 年代施乐公司帕洛阿尔托研究中心 (Xerox PARC) 在Gregor Kiczales领导下发明的一种编程范式,它使开发人员可以更好地将本不该彼此纠缠在一起的任务(例如数学运算和异常处理)分离开来。

AOP从几个不同的研究方向中发展而来。这些研究包括反射,面向对象编程中的各种扩展等等。早期的研究,是在已经有十多年来用OOP构建系统的成功经验的基础上开展的,在研究的过程中同时看到了OOP的一些固有的限制,事实上在规模巨大的系统中,比如在分布式系统中,以及在需求规则易变的系统中,这些OOP不灵活的特征是很难以领人满意的。

施乐公司帕洛阿尔托研究中心Gregor Kiczales领导的团队,起初致力于反射(Reflection)和元对象协议(metaobject protocols)方面的研究,这些研究其实是对传统的OOP编程范式的一种增强,对提供对象系统的灵活性有着非常关键作用。接着这个团队开始研究开箱实现(open implementation)的概念。最终,AOP这个编程范式诞生了,AOP关注的一个关键问题就是可扩展性, 这个问题对于让程序员在他们代码中封装他们的结构的来说非常有用。Gregor Kiczales团队从1997年开始致力于AspectJ的开发,第一次发布给外部用户是在 1998年,而1.0 release是在2001年。

示例

 

AspectJ是对Java语言的扩展,要求本地机器安装了Java JDK 1.3 以上。而AspectJ包可以从www.eclipse.org/AspcetJ/ 下载。本文测试环境所用的AspectJ包为aspectj-1.2.1.jar,操作系统为Windows XP。

下载aspectj-1.2.1.jar后,安装AspectJ的安装很简单,在命令行模式下,在aspectj-1.2.1.jar的目录下,执行 java –jar aspectj-1.2.1.jar.这个命令会启动Java,解压缩指定文件,并且开始执行该文件。如果一切正常,应该可以看到如下画面:

setupAspectj

Fig  1

 

依照提示,安装好AspectJ后,设置CLASSPATH和PATH.一般来说,PATH比较好设置,而CLASSPATH 比较麻烦,我的解决方法是直接把AspectJ安装目录下的lib/aspectjrt.jar拷贝到%Java_home%/ jre/lib/ext 和 jre安装目录的lib/ext。

现在开始测试AspectJ是否安装成功。新建一个文本文件,名为Test.java.内容如下:

class Test {

 

  public void testMethod() {

    System.out.println("Test Method");

  }

 

  public static void main(String args[]) {

    Test test = new Test();

    test.testMethod();

  }

}

再新建一个文本文件,名为TestAspct.java,内容如下:

public aspect TestAspect {

  pointcut callTestMethod() : call(public void Test.test*(..));

  before() : callTestMethod() {

    System.out.println("Before Call");

  }

  after() : callTestMethod() {

    System.out.println("After Call");

  }

}

在这两个文件存贮的目录下,执行javac Test.java , ajc Test.java TestAspect.java

然后执行java Test

就可以看到相应的信息。执行画面如下:

 

Fig 2

如果运行正常,说明Java 和AspectJ 安装成功。

熟悉Java编程的人,会感觉很好奇的一点,就是ajc 究竟利用TestAspect.java对 Test.java 做了什么些什么改动,让我们看一下Test.Class 的实际代码。

 

class Test

{

    public void testMethod() {

    System.out.println("Test Method");

    }

   

    public static void main(String[] args) {

    Test test = new Test();

    Test test_0_ = test;

    try {

        TestAspect.aspectOf().ajc$before$TestAspect$1$4a5849d2();

        test_0_.testMethod();

    } catch (Throwable throwable) {

        TestAspect.aspectOf().ajc$after$TestAspect$2$4a5849d2();

        throw throwable;

    }

    TestAspect.aspectOf().ajc$after$TestAspect$2$4a5849d2();

    }

}

 

这代码是用Java反汇编工具JODE对Test.Class 处理得来得。其实还有一个被调用得类是TestAspect.class.其反汇编而得源码为:

import org.aspectj.lang.NoAspectBoundException;

 

public class TestAspect

{

    private static Throwable ajc$initFailureCause;

    public static final TestAspect ajc$perSingletonInstance;

   

    static {

    try {

        ajc$postClinit();

    } catch (Throwable throwable) {

        ajc$initFailureCause = throwable;

    }

    }

   

    public void ajc$before$TestAspect$1$4a5849d2() {

    System.out.println("Before Call");

    }

   

    public void ajc$after$TestAspect$2$4a5849d2() {

    System.out.println("After Call");

    }

   

    public static TestAspect aspectOf() {

    if (ajc$perSingletonInstance == null)

        throw new NoAspectBoundException("TestAspect",

                       ajc$initFailureCause);

    return ajc$perSingletonInstance;

    }

   

    public static boolean hasAspect() {

    if (ajc$perSingletonInstance != null)

        return true;

    return false;

    }

   

    private static void ajc$postClinit() {

    ajc$perSingletonInstance = new TestAspect();

    }

}

这些代码其实是比较简单的,很显然ajc 编译Test.java 时,用TestAspect.java中的代码根据一定的规则对Test.java进行了改造。这一点,应该不是很出人意外的。但是这样做的意义是什么呢?

TestAspect.java 中有这样一行代码,显然很让java程序员难以理解:

pointcut callTestMethod() : call(public void Test.test*(..));

什么是pointcut?

poincut 是面向方面编程的专有名词。它指的是在一个给定的编程模型中穿越既定的职责部分(比如日志记录和性能优化)的操作。在AOP的世界里,poincut有两种类型:动态poincut和静态poincut。

动态poincut

动态poincut 是通过切入点(pointcut)和连接点(join point) 在一个方面(aspect)中创建行为的过程,连接点可以在执行时横向地应用于现有对象。动态poincut通常用于帮助向对象层次中的各种方法添加日志记录或身份认证。下面让我们花点时间了解一下动态poincut中的一些实际概念:

方面(aspect)类似于 Java 编程语言中的类。方面定义切入点(pointcut)和通知(advice),并由诸如 AspectJ 这样的方面编译器来编译,以便将poincut(包括动态的和静态的)织入(interweave)现有的对象中。

一个 连接点(join point) 是程序执行中一个精确执行点,比如类中的一个方法。例如,对象Test中的方法 testMethod() 就可以是一个连接点。 连接点是个抽象的概念;不用主动定义一个连接点。

一个 切入点(pointcut) 本质上一个用于捕捉连接点的结构。例如,可以定义一个切入点来捕捉对对象Test 中的方法 testMethod() 的所有调用。和连接点相反,切入点需要在方面中定义。

通知(advice) 是切入点的可执行代码。一个经常定义的通知是添加日志记录功能,其中切入点捕捉对对象 Test 中的 testMethod() 的每个调用,然后该通知动态地插入一些日志记录功能,比如捕捉 testMethod() 的参数。

这些概念是动态poincut的核心,虽然正如我们即将看到的,它们并不全都是静态poincut所必需的。

静态poincut
静态poincut 和动态poincut的区别在于它不修改一个给定对象的执行行为。相反,它允许通过引入附加的方法字段和属性来修改对象的结构。此外,静态poincut可以把扩展和实现附加到对象的基本结构中。

虽 然现在还无法谈及静态poincut的普遍使用——它看起来是 AOP 的一个相对未被探索(尽管非常具有吸引力)的特性——然而这一技术蕴含的潜力是巨大的。使用静态poincut,架构师和设计者能用一种真正面向对象的方法有效地建立复杂系统的模型。静态poincut允许您不用创建很深的层次结构,以一种本质上更优雅、更逼真于现实结构的方式,插入跨越整个系统的公共行为。

    很明显,上面我们讨论的Test对象所使用的技术是动态pointcut技术,那么什么是静态poincut呢?

    我们来创建一个静态pointcut的例子来说明。

    创建静态pointcut的语法和动态pointcut有很大的不同,即没有切入点和通知。给定一个对象(比如下面定义的 Foo),静态pointcut使得创建新方法、添加附加的构造函数,甚至改变继承层次都变得十分简单。

    首先新建一个java类Foo,内容如下:

public class Foo{

    public Foo(){

      

       System.out.println("in Foo()");

    }

}

 再新建一个FooNew的Java类,内容如下:

public aspect FooNew{

   

    public void  Foo.New(String a){   

      

       System.out.println(a);

       System.out.println("in Foo.New");

    }

    public static void main(String args[]) {

    Foo foo = new Foo();

    foo.New("this method is added by AspectJ");

   

  }

}

在命令行上执行 ajc Foo.java FooNew.java 。

在运行: java FooNew

产生如下结果画面:

 

 很显然,FooNew 类给Foo 这个类 增加了一个新方法Foo.New(String a)

  我们在来看一下Foo 这个类的最终的内容,用Jode反汇编得到:

public class Foo

{

    public Foo() {

    System.out.println("in Foo()");

    }

   

    public void New(String string) {

    FooNew.ajc$interMethod$FooNew$Foo$New(this, string);

    }

}

果然不出我们所料,Foo这个类新增加了一个方法New(),但是它实际上是调用FooNew类的一个方法,也就是说,实际上AspectJ 只是在Foo 中增加了一个调用接口  而已。让我们再看一下FooNew 的实际内容:

import org.aspectj.lang.NoAspectBoundException;

 

public class FooNew

{

    private static Throwable ajc$initFailureCause;

    public static final FooNew ajc$perSingletonInstance;

   

    static {

    try {

        ajc$postClinit();

    } catch (Throwable throwable) {

        ajc$initFailureCause = throwable;

    }

    }

   

    public static void ajc$interMethod$FooNew$Foo$New(Foo ajc$this_,

                           String a) {

    System.out.println(a);

    System.out.println("in Foo.New");

    }

   

    public static void ajc$interMethodDispatch1$FooNew$Foo$New(Foo foo,

                                String string) {

    foo.New(string);

    }

   

    public static void main(String[] args) {

    Foo foo = new Foo();

    ajc$interMethodDispatch1$FooNew$Foo$New

        (foo, "this method is added by AspectJ");

    }

   

    public static FooNew aspectOf() {

    if (ajc$perSingletonInstance == null)

        throw new NoAspectBoundException("FooNew", ajc$initFailureCause);

    return ajc$perSingletonInstance;

    }

   

    public static boolean hasAspect() {

    if (ajc$perSingletonInstance != null)

        return true;

    return false;

    }

   

    private static void ajc$postClinit() {

    ajc$perSingletonInstance = new FooNew();

    }

}

AOP现状

 

AOP方法学中,我们上面说的对Java原先类的改造的方式被称作织入(interweave),是意味着对pointcut的aspect与相关的代码进行重整。因而它可以由预处理器,编译期,编译后的链接器,装载器,即使编译器或者虚拟机来完成。我们现在所处的情况是不同的工具在不同的时间织入。一些新近的框架,像AspectWerkz和JBoss AOP,使用拦截器(interceptor)在运行时织入。AspectJ在编译期或者后编译期完成织入。

在创新阶段发生的一件事情就是工程上的权衡在这个技术所考虑的特定市场的限制下重新被考虑。至少对于某些应用来说,AOP动态部署的威力时值得付出这种额外的性能代价的。像Jboss AOP,完全以动态部署来工作,人们会关注其性能是否足够进行产品性的开发。当然,现在AspectJ也正在逐步的开始增加动态部署的特点。

我们上面讨论的两个示例,都是静态部署的。在性能上是对原先的编程方式没有多少影响的。这从我们反汇编的代码就可以看出。

现在,我们作为一个Java程序员可以很放心了,这个Java世界并没有发生根本的改变,让我们不知道如何前进。AspectJ的作用其实很容易理解的。所以我们需要知道的事实上是,为什么我们需要这样做?

在回答这个问题时,我们可以通过回顾OOP的历史学到很多东西,特别是80年代的那段历史。第一个需要处理的是避免过度神话某项技术。任何看过《人月神话》的人都要谨慎的对一个新技术做出过高的判断。我们必须小心不让我们的狂热使得我们听起来正在宣称AOP讲解决所有的软件问题,是Brooks说的软件产业的银弹。AOP是一个封装代码的技术——比如一些好的开发者,他们理解要解决的问题,他们也许可以用更多的人月的工作时间去完成他们的工作,AOP将帮助他们开发更好的,更干净的,更容易重用的代码而且更快更简单。它实际上也许比那做得还要多一些,因为AOP会帮助他们更容易使用OOP技术,一个可以帮助我们在软件流程中很好划分代码的现代主流封装技术。AOP是开发者工具箱中重要的新工具,解决OOP开发中类层次的软件结构上的困难问题。当然我们对此必须要很小心,因为这很容易妨碍我们对OOP方法正确应用,把一些可以用OOP技术很好解决的问题,用AOP勉强处理而导致非常复杂的流程,OOP+AOP也许会产生四不像技术。从这种角度上说,AOP也许仅仅是一种新的工具,但是我们要知道,在历史上很多时候,衡量一个技术的成熟,其实很大程度上是根据其使用的依据的。在分子生物科学,核物理,使用的工具往往决定了发展的速度。

AOP补充了OOP的不足。AOP也能够补足其他的编程范型,像过程式编程。但是因为OOP是现在的主流范型,大部分AOP工具都是对OO工具的扩展。它意味着AOP的编程实践其实是对面向对象编程实践补充。好的OO开发者学起AOP来应该相当的快,因为它们已经对它们的系统哪些方面是pointcut的有了直觉的看法。这些也应该在他们的工作中反映出来——例如,一个好的 AspectJ程序大多数是一个好的普通的Java程序。

展望

在上个世纪八十年代,那个时候有一些人认为OOP不应该添加到语言之中,而是应当把它作为一种框架,也就是一种编程的可选物。现在,这种观点已经被证明是错误的,一般人认为OOP语言是正确的方向, AOP是不是也是如此?但是当前围绕框架进行的一些创新也是有价值的,因为它帮助我们探索了新的设计权衡。

一方面,aspect(方面)下面的思想在某种意义上比对象的思想还要深。OOP技术是把一个软件系统刻画成一个类层次化的范式。OOP和过程式编程在这个点上说是类似的。但是aspect是关于有多个pointcut的分解的,并不在意整个封装的全局体系。能够意识到软件结构不一定是或者说很大程度上不是层次的这个思想是非常重要的。

在具体的系统中,有可以创建许多的pointcut。Aspects可以用于维护一个类的几个方法之间的内在一致性。它非常适合强制一种按契约进行设计(Design by contract)的编程风格。它可以用于强制各种常见的编码习惯。大量的OO设计模式也有pointcut的使用并且利用像AspectJ这种工具也能以一种模块化可重用的方式对代码进行封装。所以用AOP进行编程包括同时使用classes和aspects作为功能性单位模块编程,而且aspects和classes一样是真正的通用概念。

现在判断AOP是否比OOP更是软件产业长期寻找的银弹,还为时过早,它是不是一场新革命的导火索,还有待时间的考验。现在整个软件行业要比发现OOP编程范式时成熟多了。因而当我们期望aspect会跟class一样会有一个重大的革命的实际效果时,我们不能不考虑这是否真的能让人从以前对OOP的崇拜的幻灭中相信。
    但是很清楚的是,能够利用pointcut 对代码进行封装的能力会产生很大的实践方面的有利影响,不仅仅是在分布式的企业级应用之中,而且还在的其他复杂的软件之中。它会改善我们开发的软件的灵活性和质量,并且会减少开发耗费的时间和代价。最重要的一点也许就是,它还会使得软件开发更加有趣。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值