我们知道,J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统? 通过对J2EE容器运行机制的分析,我们可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或 线程池等性能优化机制。
这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器 服务器产品,如Tomcat JBoss、 Websphere、WebLogic等。
从J2EE系统划分为J2EE容器和J2EE应用系统两个方面,我们已经看到一种分散关注的思路(separation of concerns)。
分散关注
将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。
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()解锁。
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具体实现有以下几个项目:
AspectJ (TM): 创建于Xerox PARC. 有近十年历史,技术成熟。
缺点:过于复杂;破坏封装;需要专门的Java编译器。
动态AOP:使用 JDK的动态代理API或字节码Bytecode处理技术。
基于动态代理API的具体项目有:
JBoss 4.0 JBoss 4.0服务器
基于字节码的项目有:
aspectwerkz
spring
=====================================================================================
软件开发领域一直一来的一个核心问题就是如何能够更好地满足Dijkstra所提出的separation of concerns原则。这个原则表达了代码和开发过程的一个最为重要的特性,但是遗憾的是它只是一个原则,其中并没有告诉我们如何做才能满足这一原则。人 们在寻求能够满足这一原则的实现技术上进行了很多的探索,也取得了许多成就。其中,AOP(Aspect-Oriented Programming)就是这方面的一项最新技术。AOP的概念最初是由Xerox PARC研究中心的研究人员首先提出的,其目标是通过提供一些方法和技术,把问题领域分解成一系列的functional component和一系列横跨多个functional component的aspect,然后组合这些component和aspect,获得系统的实现。
在AOP提出很长一段时间内,基本上都处 于一种学术研究和试验阶段。不过,近几年来,情况发生了改变。由于企业应用复杂度的不断提高,对软件开发技术提出了新的挑战。如何才能使得应用开发者仅仅 关注于业务逻辑本身的开发,而不用纠缠于那些诸如安全、事务、日志等和业务逻辑无关但又是系统有效地执行业务逻辑所必须的功能呢?如果有某种技术能够把这 些与具体业务逻辑无关但又是每个应用系统不可缺少的功能块,和业务逻辑做到干净的隔离,并且又能够通过一些简单的配置或者“胶水”代码,在需要时“织入” 到应用系统中,那么不仅会大大提高开发效率,有效地降低软件的复杂性,而且在代码的清晰性、模块化、可测试性方面也会取得很好的提升。这正是AOP所要解 决的问题,下面我将从系统分析、设计活动最为基本的目标入手,介绍一下AOP到底是什么,它解决了我们所面临的哪些困难。
AOP是什么?
在我们进行软件开发时,最为理想的情况应该是:每个需求概念元素都能够在源代码中有一个直接、清晰的表示。如果我们能够做到这一点,那么就能够在根本上把变化造成的影响局部化,而这正好是团队高效地的并行开发、架构顺畅地演化以及有效达成迭代的基础。
举 例来说,在我们项目所开发的接入网软件系统中,PSTN用户端口这个概念就直接对应于程序中的一个设计表示:PstnUser类。V5链路这个概念也直接 对应于一个设计表示:Link类。下面我们来考虑这样一个需求:由于电信级的设备都有较高的可靠性要求,所以系统必须要提供主备的功能。为了能够实现主备 功能,除了硬件方面的一些要求外,在软件方面我们还必须要做到当一个业务对象的状态发生变化时,我们必须要把该状态及时地同步到备机以保证在主备切换后, 系统还能够提供正常的业务功能。可以看出这是一个单一的需求描述,但是就是这样一个简单的单一功能需求却很难被映射到一个单一的设计表示结构上面。一般来 说,我们会在业务对象中状态需要同步的地方都放上触发同步的逻辑。这种实现方法背离了需求和实现间一一映射的目标,相反这是一种需求到实现间的一对多的映 射关系。
那些在设计中所捕捉到的需求元素,一般来说很可能会随着软件开发的进展、程序的演化而发生变化。如果在我们的程序中做到了需求元素和实 现结构间的一一映射,那么这个实现结构就会非常易于修改和维护。相反,如果是一个一到多映射,那么在进行修改或者维护时就会非常得困难,因为在实现结构中 有多个地方都得被更新,并且还得保证这些更新是完全且一致的。如果遗漏掉一个地方,或者更新时没有做到一致,那么就会造成一些非常微妙的bug,定位出这 些bug很可能会花费你不少时间。
现在,我们换一个角度来看看这个问题。我们希望每个需求概念元素都能清晰地映射到一个实现结构上。我们同样也 希望每个实现结构能够清晰地映射到唯一的一个需求概念元素上。但是,在上面所列举的系统主备的例子中,我们看到了业务对象状态同步这个需求概念元素散布在 实现当中,这也意味着在业务对象实现中,不得不处理多种需求元素。对于PstnUser来说,它现在得处理至少两种需求概念:1、实现PSTN用户端口本 身的逻辑;2、在需要时进行业务状态的同步。这是一种典型的需求概念元素和实现结构间的多对一的映射关系,敏锐的读者一定会发现这种情况违反了SRP (Single Responsibility Principle)。
这种多对一的情况同样也会带来很多问题。因为在一个类中,除了要实现本 身要表示的那个需求概念外,还不得不处理很多和这个需求概念毫无关系的其他要素,比如:同步、事务、安全以及持久化等。这些和该类毫无关系的元素不但使得 该类具有多个变化的原因,同时也对该类造成了很大的侵入性,使得该类极难重用、测试。
一般来说,我们在真正的软件开发中所遇到的真实情况更加复 杂,我们基本上不可能遇到理想的概念和实现间的一一映射,而是多对多的映射。这也是导致软件如此难以维护、难以理解、如此复杂的原因。虽然面向对象方法在 解决这个问题上取得了不小的进步,但是在日益复杂的问题领域面前,它还无法使我们完全做到需求概念元素和实现结构之间清晰、干净的映射关系。而这正是 Aspect-Oriented Programming试图要解决的问题。通过AOP,我们在实现一一映射这个目标上又迈进了一大步。在AOP中引入了一个新的构造元素aspect,通 过它我们可以达成在OOP中无法做到的需求概念元素和实现结构间的一一映射,比如:前面提到的系统业务状态同步需求。
请记住,我们所追求的就是 需求概念元素和实现结构之间的一一映射。无论何时,无论何种原因,只要背离了这个目标,我们都会遇到麻烦。Structural Programming、Object-Based Programming、Object-Oriented Programming、Functional programming、Generic Programming以及Aspect-Oriented Programming这些编程范型都是为了在某个纬度上帮助我们更近地达成这个目标。
案例研究
一般来说,完全的AOP开发是需要开发环 境支持的,比如:AspectJ、Spring或者JBOSS。对于C++语言来说,到目前为止还没有一个经过实践检验的支持AOP的开发环境出现,更不 用说嵌入式系统了。不过,我们还是可以通过一些惯用的技术来手工地达成AOP的效果。在本小节中,我会通过项目中的一个实例来介绍一下比较常用的两种技 术。我还以上面提到过的PstnUser对象状态同步为例进行介绍。传统实现方法的代码片断如下:
class PstnSynAgent
{
// . . .
public:
static void AskForSynHookOn(V5IID, L3ADDR);
static void AskForSynHookOff(V5IID, L3ADDR)
// . . .
};
void PstnUser::HookOn()
{
// do something corresponding to HookOn Event.
. . .
PstnSynAgent::AskForSynHookOn(GetV5IID(), GetL3Addr() );
}
void PstnUser::HookOff()
{
// do something corresponding to HookOff Event.
. . .
PstnSynAgent::AskForSynHookOff(GetV5IID(), GetL3Addr() );
}
可以看出,上面的代码片断中存在我们在前面所提到的问题,同步逻辑散布在PstnUser对象中那些需要同步状态的方法中。而PstnUser对象本身也 违反了SRP,它不但要完成PstnUser本身的逻辑,还得去完成同步触发逻辑。下面我们将通过两种惯用的技术手法来达成AOP以消除上述味道,这两种 方法是:Decorator模式以及Policy-Based Design。
1) 使用Decorator模式达成AOP
在使用Decorator模式时,其实是把同步逻辑作为PstnUser对象的一个额外职责,放在另外一个单独的Decorator对象中,从而消除上述味道。熟悉Decorator模式的读者肯定已经明白该如何做了,这里就不再赘述。更改后的代码如下:
// 定义一个Decorator模式所需要的接口
class IPstnUser
{
//. . .
public:
virtual void HookOn() = 0;
virtual void HookOff() = 0;
};
class PstnSynDecorator : public IPstnUser
{
public:
PstnSynDecorator(IPstnUser* user) : user_(user)
{ }
void HookOn()
{
user_->HookOn();
PstnSynAgent::AskForSynHookOn(user_->GetV5IID(),
user_->GetL3Addr());
}
void HookOff()
{
user_->HookOff();
PstnSynAgent::AskForSynHookOff(user_->GetV5IID(),
user_->GetL3Addr());
}
// . . .
private:
IPstnUser* user_;
};
class PstnUser : pubic IPstnUser
{
public:
void HookOn()
{
// do something corresponding to HookOn Event.
. . .
}
void HookOff()
{
// do something corresponding to HookOff Event.
. . .
}
// . . .
};
// 在创建对象时,需要这样进行
IPstnUser* pstnUser = new PstnUser(iid, addr) //创建普通PstnUser对象
IPstnUser* synPstnUser = new PstnSynDecorator(pstnUser) //为普通PstnUser对象增加同步功能。
这样,就达成了业务对象状态同步需求和PstnSynDecorator类直接对应,而Pstn用户端口的功能需求和PstnUser直接对应。
2) 使用Policy-Based Design达成AOP
在 这种方法中,其实是把业务对象状态同步逻辑作为一个Policy,通过使用Policy-Based Design手法,把这个policy和业务对象逻辑本身组合起来,从而消除上述味道。Policy-Based Design技术在C++中是通过template和multiple-inheritance实现的。关于Policy-Based Design的详细介绍,请参见《Modern C++ Design》一书,这里也不再介绍。更改后的代码如下:
template <class SynPolicy, class UserPolicy>
class PstnUserWrapper : private SynPolicy, private UserPolicy
{
// . . .
public:
void HookOn()
{
UserPolicy::HookOn();
SynPolicy::AskForSynHookOn(GetV5IID(),GetL3Addr());
}
void HookOff()
{
UserPolicy::HookOff();
SynPolicy::AskForSynHookOff(GetV5IID(),GetL3Addr());
}
// . . .
};
class PstnUser
{
public:
void HookOn()
{
// do something corresponding to HookOn Event.
. . .
}
void HookOff()
{
// do something corresponding to HookOff Event.
. . .
}
// . . .
};
// 在创建对象时,需要这样进行
tyepdef PstnUserWrapper<PstnSynAgent, PstnUser > SynPstnUser;
SynPstnUser synPstnUser;
这种方法同样也解决了上面提到的问题,达成了需求元素和实现结构间的一一对应关系。相比起来,使用Decorator模式的方法具有更大的动态性,如果需要这种额外的动态性的话,可以选择使用Decorator模式的方法。
不 过请注意,上面两种方法只是应用了AOP的思想,并不是完全意义上的AOP。在支持纯粹AOP的开发环境中,业务状态同步这种“横切”逻辑是由开发环境 (编译器(扩展)或者run-time system等)根据相应的定义描述信息自动“织入”的,对象模型本身根本感觉不到这个“横切”逻辑的存在。在缺少AOP开发环境支持的情况下,我们只能 通过手工的方法进行“织入”。
一点建议
如果在进行Use Case分析时能够正确运用AOP技术,我们就获取了另外一个纬度的自由。这个额外的自由度能为我们带来很多好处。首先,我们可以以更为干净、清晰的方式 获取Use Case,使Use Case之间的耦合降到最低,并且还能最大程度地做到Use Case和实现结构间地一对一映射关系,这样开发团队就可以独立、并行地进行开发,大大提高开发生产力,并且有效地降低了开发小组间无谓的沟通、协调工作 量。此外,这个自由度可以使我们在设计实现结构时更加模块化、更加富有表达力,从而为更加有效的迭代、重构提供了一个良好的基础。
但是,在我们进 行开发时,一定要注意不要滥用AOP技术。和任何一种其他技术一样,AOP也是靠引入一定程度的代价来解决我们所面临的问题的。如果我们能够理解AOP所 真正要解决的问题,在实际开发中正确的应用了这种技术,那么这种代价和所带来的好处相比是值得的。但是,如果不恰当地应用了AOP技术,那么非但不会带来 任何好处,相反还会使得程序结构变得更加晦涩。希望上面的介绍能够为你在决定“是否该考虑使用AOP?”这个问题时提供一个依据。我相信如果你真得理解了 上面对AOP的介绍,就不会在拿到了AOP这个“榔头”后,发现到处都是pointcut和advice之类的“钉子”了 :)
====================================================================================
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()解锁。
AOP应用范围
很明显,AOP非常适合开发J2EE容器服务器,目前JBoss 4.0正是使用AOP框架进行开发。
具体功能如下:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
【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具体实现有以下几个项目:
AspectJ (TM): 创建于Xerox PARC. 有近十年历史,成熟
缺点:过于复杂;破坏封装;需要专门的Java编译器。
动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。
基于动态代理API的具体项目有:
JBoss 4.0 JBoss 4.0服务器
nanning 这是以中国南宁命名的一个项目,搞不清楚为什么和中国相关?是中国人发起的?
基于字节码的项目有:
aspectwerkz ,spring
面向Agent的编程:Agent Orented Progarmming来自人工智能领域,号称第六代编程语言,与面向对象的编程:即:OOP相比,它更具主动性、学习型、协同性。 具体可查找Agent技术相关知识。