一般情况下,对象是由一行行的代码“粘合”在一起的。创建这个对象。创建那个对象。为那个对象(其值为这个对象)设置属性。其间还点缀着一些用户数据。将一切搅拌在一起。这是现代程序员在进行设计和编码时经常做的事情。
将多个这样的类组合起来形成具有一定功能的组件,而很多这样的组件以这种方式连接起来会出现这样的问题:要实现不同的方法时,需要花费大量时间编写同样的代码。这些代码行中往往会有以下操作:将这个方法的活动记录日志到一个文件中以备调试,运行安全性检查,启动一个事务,打开一个数据库连接,记住捕捉 C++ 异常或者 Win32 结构化异常以转换为 COM 异常,还要验证参数。而且,还要切记在方法执行完之后销毁方法开始时的设置。还有很多的事务机制、安全机制以及对象池或线程池等性能优化机制。
这种重复代码经常出现的原因在于,开发人员被训练为根据软件项目需求中的名词来设计系统。如果设计的是银行系统,Account类和Customer 类必不可少,它们都将自己独特的详细信息收集到一处,但是它们的每个方法也都需要进行日志、安全检查、事务管理等操作。区别在于,日志等操作是一些与特定应用无关的系统方面。
这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成。
这样,我们已经有了一种分散关注的思路(separation of concerns)。
将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。这就是分散关注(separation of concerns)。
AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。
面向方面编程 (AOP) 是施乐公司帕洛阿尔托研究中心 (Xerox PARC) 在20世纪 90 年代发明的一种编程范式,它使开发人员可以更好地将本不该彼此纠缠在一起的任务(例如数学运算和异常处理)分离开来。 AOP 方法有很多优点。首先,由于操作更为简洁,所以改进了性能。其次,它使程序员可以花费更少的时间重写相同的代码。总之,AOP 能够为不同过程提供更好的封装性,提高未来的互操作性。
是什么使软件工程师都希望自己能成为硬件工程师呢?自从函数发明以来,程序员花费了大量时间(及其老板的大多数资金)试图设计这样的系统:它们不过是一些组合模型,由其他人创建的部件构成,布置成独特的形状,再覆盖上一些悦目的颜色。函数、模板、类、组件等等一切,都是软件工程师自己创建“软件集成电路”(模拟硬件设计师的电子器件)的种种尝试。
我把这些都归咎于 Lego(乐高玩具)。把两个玩具块(即组件)拼起时发出的悦耳的咔哒声很让人上瘾,会促使许多程序员发明一种又一种新的封装和重用的新机制。这方面最新的进展就称为面向方面编程 (AOP) 。 AOP 的核心是安排(一个摞在另一个之上)组件的一种方式,可以获得其他种类基于组件的开发方法无法得到的重用级别。这种安排是在客户端和对象之间的调用堆栈中进行的,其结果是为对象创建了一种特定的环境。这种环境正是 AOP 程序员主要追求的东西。
AOP是什么?
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。
为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。
使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:
abstract class Worker{
abstract void locked();
abstract void accessDataObject();
abstract void unlocked();
}
这样做的缺点:
accessDataObject()方法需要有“锁”状态之类的相关代码。
Java只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Worker的父类,将无法方便实现。重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。
仔细研究这个应用的“锁”,它其实有下列特性:
“锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
“锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。
“锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。
因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect(方面)。
在这个应用中,“锁”方面(aspect)应该有以下职责:
提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。
什么是方面?
在考虑对象及对象与其他对象的关系时,我们通常会想到继承这个术语。例如,定义某一个抽象类 — Dog 类。在标识相似的一些类但每个类又有各自的独特行为时,通常使用继承来扩展功能。举例来说,如果标识了 Poodle,则可以说一个 Poodle 是一个 Dog,即 Poodle 继承了 Dog。到此为止都似乎不错,但是如果定义另一个以后标识为 Obedient Dog 的独特行为又会怎样呢?当然,不是所有的 Dogs 都很驯服,所以 Dog 类不能包含 obedience 行为。此外,如果要创建从 Dog 继承的 Obedient Dog 类,那么 Poodle 放在这个层次结构中的哪个位置合适呢?Poodle 是一个 Dog,但是 Poodle 不一定 obedient;那么 Poodle 是继承于 Dog 还是 Obedient Dog 呢?都不是,我们可以将驯服看作一个方面,将其应用到任何一类驯服的 Dog,我们反对以不恰当的方式强制将该行为放在 Dog 层次结构中。
在软件术语中,面向方面的编程能够独立于任何继承层次结构而应用改变类或对象行为的方面。然后,在运行时或编译时应用这些方面。举一个关于 AOP 的示例,然后进行描述,说明起来比较容易。首先,定义四个关键的 AOP 术语,这很重要,因为我将反复使用它们:
? 接合点 (Joinpoint) — 代码中定义明确的可识别的点。
? 切点 (Pointcut) — 通过配置或编码指定接合点的一种方法。
? 通知 (Advice) — 表示需要执行交叉切割动作的一种方法
? 混入 (Mixin) — 通过将一个类的实例混入目标类的实例引入新行为。
为了更好地理解这些术语,可以将接合点看作程序流中定义好的一点。说明接合点的一个很好的示例是:在代码调用一个方法时,发生调用的那一点被认为是一个接合点。
切点用于指定或定义希望在程序流中截获的接合点。切点还包含一个通知,该通知在到达接合点时发生。因此,如果在一个调用的特定方法上定义一个切点,那么在调用该方法或接合点时,AOP 框架将截获该切点,同时还将执行切点的通知。
通知有几种类型,但是最常见的情况是将其看作要调用的另一个方法。在调用一个带有切点的方法时,要执行的通知将是另一个要调用的方法。要调用的这个通知或方法可以是对象中被截获的方法,也可以是混入的另一个对象中的方法。
AOP有必要吗?
当然,上述应用范例在没有使用AOP情况下,也得到了解决,例如JBoss 3.XXX也提供了上述应用功能,但是没有使用AOP。
但是,使用AOP可以让我们从一个更高的抽象概念来理解软件系统,AOP也许提供一种有价值的工具。可以这么说:因为使用AOP结构,现在JBoss 4.0的源码要比JBoss 3.X容易理解多了,这对于一个大型复杂系统来说是非常重要的。
从另外一个方面说,好像不是所有的人都需要关心AOP,它可能是一种架构设计的选择,如果选择J2EE系统,AOP关注的上述通用方面都已经被J2EE容器实现了,J2EE应用系统开发者可能需要更多地关注行业应用方面aspect。
AOP具体实现
AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),将 AOP 用于多数大型系统或关键的生产系统还不完全成熟,但是随着语言支持的提高,AOP 的应用将更容易。另外,提高支持也是新的软件开发范例,例如利用面向方面的编程的软件工厂。目前有几种可用的 AOP 框架,每个框架都有其自己的方法、正面属性和负面属性。 目前AOP具体实现有以下几个项目:
?AspectJ (TM) : 创建于Xerox PARC. 有近十年历史,成熟
缺点:过于复杂;破坏封装;需要专门的Java编译器。
?动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。
基于动态代理API的具体项目有:
JBoss 4.0 JBoss 4.0服务器
JAC (Java Aspect Components) — 是一个应用服务器。它为Java2平台、用于Java开发的企业开发环境(J2EE)、和基于Web的分布式应用,提供开放式资源的又一个选择(在GNU 次常规公共许可证下发布)。JAC包括统一模型语言(UML)IDE,该UML IDE模块化应用商业逻辑并且自动生成和编译纯商业逻辑Java类。这些类,在JAC容器内执行,可从一组技术和/或商业的横切关系(crosscutting concerns)如:数据持久性、认证、配置文件管理、访问权限检测、演示、和负载平衡中无缝地受益。基于面向方面编程技术(AOP)的JAC将这些关系( concerns)从应用程序的核心商业逻辑中分离出来。
nanning 这是以中国南宁命名的一个项目,搞不清楚为什么和中国相关?是中国人发起的?
?基于字节码的项目有:
aspectwerkz — 基于Java的简单、动态、轻量级、强大的AOP框架。既强大又简单,有助于更容易的集成AOP到新的或已存在的项目中。
spring
Spring.NET — 流行的 Java Spring 框架的一个 .NET 版本。在下一个版本中将实现 AOP。
? DynamicAspects — 能够让你使用java编写的面向切面的程序设计,它使用在Sun JDK 1.5中介的"instrumentation"与"agent",Aspects能够软件各模块之间的关系在运行期安装与使用。
? dynaop框架 — 使用一个基于运行时的编程机制将AOP代码插入对象中,而不是返回一个具有特征代码的对象。AOP将是面向对象设计(OO)的一个新的领域。
? CAESAR — 是一个新的与Java兼容的AOP语言。所有java程序多能使用CAESAR。
? PROSE — 是一个动态编排(weaving)工具(允许在运行期插入或抽取aspects)。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。
? Encase — Encase 在运行时期间应用能够单独添加到对象的方面。
? Aspect# — 一个针对 CLI 的 AOP 联合兼容框架,提供声明和配置方面的内置语言。
? RAIL — RAIL 框架在虚拟机 JIT 类时应用方面。
? Eos — 用于 C# 的一个面向方面的扩展。