面向对象技术很好地解决了软件系统中角色划分的问题。借助于面向对象的分析、设计和实现技术,开发者可以将问题领域的“名词”转换成软件系统中的对象,从而很自然地完成从问题到软件的转换.
但是,问题领域的某些需求却偏偏不是用这样的“名词”来描述的.我的一个朋友就曾经遇到这样的问题:需要对系统中的某些方法进行日志记录,这种需要记录方法散布在40多个类中。面对这种需求,应该怎么办呢?最直接的办法就是:创建一个起类(或接口),将日志的功能放在其中,并让所有需要日志功能的类继承这个起类(或接口).如果这个需求是后期提出的.需要修改的地方就会分散在40多个文件(如果是C+十,这个数量还可能加倍)中。这样大的修改量,无疑会增加出错的几率,并且加大系统维护的难度。
人们认识到,传统的程序经常表现出一些不能自然地适合单个程序模块或者几个紧密相关的程序模块的行为 例如日志记录、对上下文敏感的错误处理、性能优化以及设计模式等等、我们将这种行为称为“横切关注点(crosscuttingconcern)”,因为它跨越了给定编程模型中的典型职责界限。如果使用过用于核切关注点的代码,您就会知道缺乏模块性所带来的问日。因为横切行为的实现是分散的,开发人员发现这种行为难以作逻辑思维、实现和更改.
因此,面向方面的编程(Aspect-OrientedProgramming,AOP)应运而生。AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化.通过划分Aspect代码,横切关注点变得容易处理。开发者可以在编译时更改、插入或除去系统的Aspect,甚至重用系统的Aspect.
更重要的是,AOP可能对软件开发的过程造成根本性的影响。我们可以想象这样一种情况:OOP只用于表示对象之间的泛化一特化(generalization-specialization)关系(通过继承来表现),而对象之间的校向关联则完全用AOP来表现。这样,很多给对象之间横向关联增加灵活性的设计模式(例如Decorator、Role Object等)将不再必要.
一种编程思想是否真正优秀,只有从实现语言上才能看到。施乐公司帕洛阿尔托研究中心(Xerox PARC)开发了第一个AOP的开发环境——AsPectJ ,这个工具提供了一整套的语法,能够清楚地描述横切关注点,并将其织入到Java源代码中。织入后的代码仍是标准Java代码,因此AspectJ不会影响Java的移植能力。此外,AspectJ提供了一个独立的IDE,并且能够嵌入到Jbuilder、Forte等Java开发环境之中,无缝地提供AOP的能力。关于AspectJ,读者可以在http://www.aspectj.org找到更多的信息。
但是,现在的AOP还处于相当不完善的阶段:它只能应用于很少的几种语言环境下,并且必须掌握源代码才能进行织入.但以RUP之父Ivar Jacobson为代表的科学家们仍对AOP推崇备至:他认为AOP将最终改变整个软件开发的方式,并且更完美地实现“用例驱动”的开发思想.
利用AOP分离软件关注点
一个关注点(concern)就是一个特定的目的,一块我们感兴趣的区域。从技术的角度来说,一个典型的软件系统包含一些核心的关注点和系统级的关注点。举个例子来说,一个信用卡处理系统的核心关注点是借贷/存入处理,而系统级的关注点则是日志、事务完整性、授权、安全及性能问题等,许多关注点——我们叫它横切关注点(crosscutting concerns)——会在多个模块中出现,使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进。
AOP能够比上述方法更好地分离系统关注点,从而提供模块化的横切关注点。
在这篇文章里,我首先会解释横切关注点在软件系统中引起的问题,接着我会介绍AOP是怎样解决横切关注点问题的。
软件编程方法的演进在计算机科学的早期阶段,开发人员使用简单的机器级代码来编程。不幸的是,程序员得花费更多时间来考虑一种特定机器的指令集而不是手中需要解决的问题本身。慢慢地,我们转而使用允许对底层机器做某种抽象的高级语言。然后是结构化语言,我们可以把问题分解成一些必要的过程来完成任务。但是,随着复杂程度的增加,我们又需要更适合的技术。面向对象的编程方式(OOP)使我们可以把系统看作是一批相互合作的对象。类允许我们把实现细节隐藏在接口下。多态性为相关概念提供公共的行为和接口,并允许特定的组件在无需访问基础实现的前提下改变特定行为。
编程方法和语言决定了我们和计算机交流的方式。每一种新的方法学都提出一种新的分解问题的方法:机器码、伪代码、过程和类等。每种新的方法学都使得从系统需求到编程概念的映射更加自然。编程方法学的发展让我们可以建立更加复杂的系统,这名话反过来说也地,我们能够建立更加复杂的系统是加为这些技术允许我们处理这种复杂度。
现在,大多数软件项目都选择OOP的编程方式。确实,OOP已经表明了它处理一般行为的能力,但是,我们将会看到(或许你已经感觉到了):OOP不能很好地处理横越多个——经常是不相关的——模块的行为。相比之下,AOP填补了这个空白,它很可能会是编程方法学发展的一个里程碑。
把系统看作一批关注点我们可以把一个复杂的系统看作是由多个关注点来组合实现的。一个典型的系统可能会包括几个方面的关注点,如业务逻辑、性能,数据存储、日志和调度信息、授权、安全、线程、错误检查等,还有开发过程中的关注点,如易懂、易维护、易追查、易扩展等,图1演示了由不同模块实现的一批关注点组成一个系统。

图2把需求比作一束穿过三棱镜的光。我们让需求之光通过鉴别关注点的三棱镜,就会区别出每个关注点。

开发人员建立一个系统以满足多个需求,我们可以大致地把这些需求分类为核心模块级需求和系统组需求。很多系统级需求一般来说是相互独立的,但它们一般都会横切许多核心模块。举个例子来说,一个典型的企业应用包含许多横切关注点,如验证、日志、资源地、系统管理、性能及存储管理等,每一个关注点都牵涉到几个子系统,如存储管理关注点会影响到所有的有状态业务对象。
让我们来看一个简单的例子,考虑一个封装了业务逻辑的类的实现框架:
public class SomeBusinessClass extends otherBusinessClass{
//核心数据成员
//其它数据成员:日志流,保证数据完整性的标志位等
//重载基类的方法
public void performSomeOperation (OperationInformation info) {
//安全性验证
//检查传入数据是否满足协议
//锁定对象以食品店当其他线程访问时的数据完整性
//检查缓存中是否为最新信息
//记录操作开始执行时间
//执行核心操作
//记录操作完成时间
//给对象解锁
}
//一些类似操作
public void save(PersitanceStorage ps){
}
}
在上面的代码中,我们注意到三个问题:首先,其它数据成员不是这个类的核心关注点;第二performSomeOperation()的实现做了许多核心操作之外的事,它要处理日志、验证、线程安全、协议验证和缓存管理等一些外围操作,而且这些外围操作同样也会应用于其他类;第三,save()和load()执行的持久化操作是否构成这个类的核心是不清楚的.
横切关注点的问题虽然横切关注点会跨越多个模块,但当前的技术倾向于使用一维的方法学来处理这种需求,把对应需求的实现强行限制在一维的空间里.这个一维空间就是核心模块级实现.其他需求的实现被嵌入在这个占统治地位的空间.换句话说,需求空间是一个n维空间,而实现空间是一维空间,这种不匹配导致了糟糕的需求到实现的映射。
表现用当前方法学实现横切关注点是不好的.它会带来一些问题,我们可以大致把这些问题分为两类。
代码混乱:软件系统中的模块可能要同时兼顾几个方面的需要.举例来说,开发者经常要同时考虑业务逻辑、性能、同步,日志和安全等问题,兼顾各方面的需要导致相应 关注点的实现元素同时出现,引起代码混乱.
代码分散:由于横切关注点本来就涉及到多个模块.相关实现也就得遍布在这些模块里.如在一个使用了数据库的系统里,性能问题就会影响所有访问数据库的模块。这导致代码分散在各处.
结果混乱和分散的代码会从多个方面影响系统的设计和开发:
可读性差:同时实现几个关注点模糊了不同关注点的实现,使得关注点与其实现之间的对应关系不明显。
低产出:同时实现几个关注点把开发人员的注意移到外围关注点,导致生产效率降低.
低代码重用率: 由于一个模块实现多个关注点,因此其他需要类似功能的系统不能马上使用该模块.进一步降低了生产效率。
代码质量差:混乱的代码掩盖了代码中隐藏的问题。而且,由于同时要处理多个关注点.应该特别注意的关注点得不到应有的关注.
难以扩展:狭窄的视角和有限的资源总是产生仅注意当前关注点的设计.新的需求导致从新实现.由于实现不是模块化的,就是说实现牵涉到多个模块,为了新需求修改子系统可能会带来数据的不一致,而且还需相当规模同测试来保证这些修改不会带来bug。
当前解决方法由于多数系统中都包含横切关注点、自然的已经形成了一些技术来模块化横切关注点的实现,这些技术包括:混入类、设计模式和面向特定问题域的解决方式。
使用混入类,你可以推迟关注点的最终实现.基本类包含一个混入类的实例,允许系统的其他部分设置这个实例。举个例子来说,实现业务逻辑的类包含一个混入的logger,系统的其他部分可以设置这个logger已得到合适的日志类型,比如logger可能被设置为使用文件系统或是消息中间件。在这种方式下,虽然日志的具体实现被推迟,基本类还是必须在所有写日志的点调用日志操作和控制日志信息的代码。
行为型设计模式,如Visitor和Template Method模式,也允许你推迟具体实现。但是也就像混入类一样,操作的控制——调用visitor或template Method的逻辑——仍然留给了基本类。
面向特定问题域的解决方式,如框架和应用服务器,允许开发者用更模块化的方式处理某些横切关注点。比如E J B(Enterprise JavaBean)架构,可以处理安全、系统管理、性能和容器管理的持久化(container-managed persistence)等横切关注点。B e a n与数据库的映射,但是大多数情况下,开发者还是要了解存储结构。这种方式下,你用基于XML的映射关系描述器来实现于数据持久化相关的横切关注点。
面向特定问题域的解决方式提供了解决特定问题的专门机制,它的缺点是对于每一种这样的解决方式开发人员都必须重新学习,另外,由于这种方式是特定问题域相关的,属于特定问题域之外的横切关注点需要特殊的对待。
设计师的两难局面。好的系统设计师不仅会考虑当前需求,还会考虑到可能会有的需求以避免到处打补丁。这样就存在一个问题预知将来是很困难的,如果你漏过了将来可能会有的横切关注点的需求,你将会需要修改或甚至是重新实现系统的许多部分;从另一个角度来说,太过于关注不一定需要的需求会导致过分设计的、难以理解的、臃肿的系统。所以系统设计师处在这么一个两难局面中:怎么设计算不了过分设计?应该宁可设计不足还是宁可过分设计?
举个例子来说,设计师是否应该在系统中包含现在并不需要的日志机制?如果是的话,哪里是应该写日志的点?日志应该记录那些信息相似的例子还有关于性能的优化问题,我们很少能预知瓶颈的所在。常用的方法是建立系统,profile它,然后翻新系统以提高性能,这种方式可能会依照profiling而修改系统的很多部分。此外,随着时间的流逝,由于使用方式的变化,可能还会产生新的瓶颈。类库设计师的任务更困难,因为他很难设想出所有对类库的使用方式。
总而言之,设计师很难顾及到系统可能需要处理的所有关注点。即使是在已经知道了需求的前提下,某些建立系统时需要的细节 也可能不能全部得到,整体设计就面临着设计不足/过分设计的两难局面。
AOP基础
到目前为止的讨论说明模块化横切关注点是有好处的。研究人员已经尝试了多种方法来实现这个任务,这些方法有一个共同的主题:分离关注点。A O P是这些方法中的一种,它的目的是清晰的分离关注点来解决以上提到的问题。
AOP, 从其本质上讲,使你可以用一种松散耦合的方式来实现独立的关注点,然后 组合这些实现来建立最终系统、用它所建立的系统是使用松散偶合的,模块化实现的横切关注点来搭建的、与之对照 用OOP建立的系统则是用松散耦合的模块化实现的一般关注点来实现的。在AOP中,这些模块化单元叫“方面(aspect)”,而在OOP中,这些一般关注点的实现单元叫做类。
AOP 包括三个清晰的开发步骤:
方面分解:分解需求撮出横切关注点。在这一步里,你把核心模块级关注点和系统级的横切关注点分离开来、就前面所提到的信用卡例子来说,你可以分解出三个关注点:核心的信用卡处理、日志和验证。
关注点实现:各自独立的实现这些关注点,还用上面信用卡的例子,你要实现信用卡处理单元、日志单元和验证单元。
方面的重新组合:在这一步里,方面集成器通过创建一个模块单元—一方面来指定重组的规则,重组过程—一也叫织入(weaving)或结合(integrating)——则使用这些信息来构建最终系统、还拿信用卡的那个例子来说你可以指定(用某种AOP的实现所提供的语言)每个操作的开始和结束需要记录,并且每个操作在涉及到业务逻辑之前必须通过验证。

AOP与OOP最重要的不同在于它处理横切关注点的方式.在AOP中 每个关注点的实现都不知道其它关注点是否会“关注”它,如信用卡处理模块并不知道其它的关注点实现正在为它做日志和验证操作。它展示了一个从OOP转化来的强大的开发范型。
注意:一个AOP实现可以借助其它编程范型作为它的基础,从而原封不动的保留其基础范型的优点。例如,AOP可以选择OOP作为它的基础范型,从而把OOP善于处理一股关注点的好处直接带过来。用这样一种实现,独立的一般关注点可以使用OOP技术、这就像过程型语言是许多OOP语言的基础一样。
AOP语言剖析
就像其他编程范型的实现一样,AOP的实现由两部分组成:语言规范和实现。语言规范描述了语言的基础单元和语法;语言实现则按照语言规范来验证代码的正确性,并把代码转成目标机器可执行的形式。这一节,我来解释一下AOP组成部分。
AOP语言规范
从抽象的角度看来 一种AOP语言要说明下面两个方面:
关注点的实现:把每个需求映射为代码,然后,编译器把它翻译成可执行代码。由于关注点的实现以指定过程的形式出现,你可以使用传统语言如C、C++、JAVA等。
织入规则规范:怎样把独立实现的关注点组合起来形成最终系统呢?为了这个目的 需要建立一种语言来指定组合不同的实现单元,以形成最终系统的规则。这种指定织入规则的语言可以是实现语言的扩展,也可以是一种完全不同的语言。
AOP语言的实现
AOP的编译器执行两步操作:
l 组装关注点
2 组装结果转成可执行代码。
AOP实现可以用多种方式实现织入, 包括源码到源码的转换、它预处理每个方面的源码,产生织入过的源码,然后把织入过的源码交给基础语言的编译器, 产生最终可执行代码。比如 使用这种方式,一个基于Java的AOP实现可以先把不同的方面转化成Java源代码,然后让Java编译器把它转化成字节码.也可以直接在字节码级别执行织入; 毕竟 字节码本身也是一种源码。此外,底层的执行系统—一Java虚拟机—一也可以设计为支持AOP的。基于Java的AOP实现如果使用这种方式的话,虚拟机可以先装入织入规则,然后对后来装入的类都应用这种规则、也就是说,它可以执行just-in-time的方面织入。
AOP的好处
AOP可帮助我们解决上面提到的代码混乱和代码分散所带来的问题,它还有一些别的好处:
模块化横切关注点:AOP用最小的耦合处理每个关注点,使得即使是横切关注点也是模块化的。这样的实现产生的系统,其代码的冗余小。模块化的实现还使得系统容易理解和维护。
系统容易扩展:由于方面模块根本不知道横切关注点,所以很容易通过建立新的方面加入新的功能.另外,当你往系统中加入新的模块时,已有的方面自动横切进来,使系统易于扩展。
设计决定的迟绑定:还记得设计师的两难局面吗?使用AOP 设计师可以推迟为将来的需求作决定,因为他可以把这种需求作为独立的方面很容易地实现。
更好的代码重用性:AOP把每个方面实现为独立的模块, 模块之间是松散耦合的.举例来说,你可以用另外一个独立的日志写入器方面来替换当前的,用于把日志写入数据库,以满足不同的日志写入要求。松散藕合的实现通常意味着更好的代码重用性,AOP在使系统实现松散出合这一点上比OOP做得更好。
AspectJ:一个Java的AOP实现
AspectJ是一个可免费获得的、由施乐公司帕洛阿尔托研究中心(Xerox PARC)开发的、Java的AOP实现。它使用java作为单个关注点的实现语言,并扩展Java以指定织入规则.
另外,AspectJ允许以多种方式用方面和类建立新的方面,你可以引入新的数据成员和方法或是声明一个新的类来继承和实现另外的类或接口。
AspectJ的织入器——AspectJ的编译器——负责把不同的方面组合在一起,由于AspectJ编译器建立的最终系统是Java字节码,因此,它可以运行在任何符合Java标准的虚拟机上。而且,AspectJ还提供了一些工具,例如调试器和Java IDE集成等。
我需要AOP吗?
AOP仅仅是解决设计上的缺点吗?在AOP里,每个关注点的实现的并不知道是否有其它关注点关注它,这是AOP和OOP的主要区别。在AOP里,组合的流向是从横切关注点到主关注点,而OOP则相反。但是,OOP可以和AOP很好地共存。比如,你可以使用一个混入类来做组合,既可以用AOP实现,也可以用OOP实现,这取决你对AOP的接受程度,在这两种情况下,实现横切关注点的混入类实现都无需知道它自己是被用在类中还是被用在方面中。举个例子来说,你可以把一个日志写入器接口用作某些类的混入类或是用作一个日志方面,因而,从OOP到AOP 是渐进的。
了解AOP
在这篇文章里,你看到了横切关系带来的问题,这些问题当前解决方法以及这些方法的缺点。你也看到了AOP 是怎样克服这些缺点的,AOP的编程方式试图模快化横切关注点的实现,提供了一个更好更快的软件开发方式。
如果你的系统中涉及到多个横切关注点,你可以考虑进一步了解AOP,了解它的实现和它的好处。AOP很可能会是编程方式的一个里程碑。
作者:Thatway (mailto:outhatway (at) hotmail (dot) com)
来自:http://www.hibernate.org.cn
目 录
为什么重要
您应该使用UML吗?一个字:是!……新的书、文章等等将会全部以UML作为符号。……如果你正要开始使用建模符号,您就应该直接学习UML。
—— Martin Fowler 1997
统一建模语言UML(Unified Modeling Language)是一套用于面向对象系统建模的标准符号,在20世纪90年代几经波折,终于得到对象管理组织OMG(Object Management Group)的支持,成为业界符号系统的统一标准。
因此,了解它是学习后面技术的前提。这并不是夸张,在看懂(并非精通)UML以前,我压根没明白《设计模式》,因为《设》里面就用到象UML那样的语言——OMT。
那UML无疑是重要,但它能很好的胜任建模工作吗?关于这个问题,我没有能力给您一个完美的答复,相关的“讨论”也在继续,但下面的几个事实相信会给您信心。
1。UML吸取多种建模语言的优点,包括三位全球顶级方法学家的贡献。 2。它在风雨中崛起,经受考验。 3。UML得到OMG组织及其成员的支持。 4。相当多的业界巨头们也在使用着它。
学习旅程
UML体系是如此复杂,可能会让您觉得难以把握。但是我们只要有选择性的学习就足够了,剩下的当日后有需要时再深入研究。
在学习之前,我们先来做一个说明,UML仅是一门语言,学习UML不等同于学习系统建模,它们的关系就好比学习中文和学习文章写作那样。只是很多情况下,我们都会把它们联系在一起而已。
视图和图
那好,就让我们开始吧,先来浏览一下UML的全貌。由于我们很难只从一个角度去完整描述一个系统的所有方面。因此UML提供了以下五种视图,它们分工合作,又互相补充。
1) 用况视图(Use Case View) 2) 设计视图(Design View)或逻辑视图(Logical View) 3) 进程视图(Process View) 4) 实现视图(Implementation View)或组件视图(Component View) 5) 实施视图(Deployment View)而这五个视图又分别用到以下九种图中的一种或几种。
1) 用况图 2) 类图 3) 对象图 4) 顺序图 5) 协作图 6) 状态图 7) 活动图 8) 构件图 9) 实施图相关资源
好,看一下相关资源。
1) 《UML用户指南》
此书出自名家,只是部分翻译欠佳。阅读时弄清楚上述五个视图的概念和几种常用的图的表示即可,初次阅读不必深究。
2) 《UML和模式应用》
书中示范如何结合UML以增量方式开发一个系统,着重介绍了OO分析的技巧和法则。内容稍嫌罗嗦,但不失为一本好书。
3) 《UML Distilled》
另一本入门好书,作为普通使用已经足够。
4) 《非程序员》第二期之《用UML设计Java应用程序》
阅读这一短篇文章,可以快速了解如何在实际项目中使用UML。
UML的官方网站,可以找到很多有用资料。
它发行《非程序员》电子杂志和记录很多中文文档,还有一个非常活跃的讨论组。
7) http://www.csdn.net/develop/
8) http://www-900.ibm.com/developerworks/cn
这两个网站可以搜索到很多UML的中文文章,只是比较零散,不大适合系统学习。
再来看一下UML工具。在这方面我没有很好的经验,且看看别人怎么说。
1) 《非程序员》第一期的“选择一种UML建模工具”介绍了评价UML工具的一系列标准。
2) UML官方网站资源页的“UML Tools”栏(链接)列出了极多的UML工具,可供选择。
聚合、关联和引用
最后,我们讨论一下几个具有“争议”性的概念——聚合(Aggregation)、组合(Composition)、相识(Acquaintance)、关联(Association)、依赖(Dependence)和引用(Reference)。它们极具相似性,在代码的实现上有些甚至是完全一样的,然而从概念上理解和区分它们对我们的系统分析和设计是有重要意义的。
聚合是指一个对象拥有另一个对象,仅强调“拥有”。而组合是指一个对象是另一个对象的一部分,强调“不可分割”,两个对象具有相同的生命周期。两者的差别就好比创立一间公司时您可以不要雇员(拥有),但创造一个人时您却不能丢掉了他的心(不可分割)。
关联和依赖都是指一个对象知道另一个对象。区别在于关联是一种结构关系,表现为一个对象能够获得另一个对象的实例引用并调用它的服务(即使用它);依赖是一种使用关系,表现为一个对象仅仅是调用了另一个对象的服务。相识既可能是关联,也可能是依赖。
引用是指那些指向对象的类属性。实现组合、聚合和关联时无可避免的要用到引用,但实现依赖时却不一定用到。
总的来说,关联和依赖是同级的;组合是一种聚合,而聚合是一种关联;引用则是相对独立的。
与此相关的文章有:
1) 《UML用户指南》第10章,Booch+详细讲述了依赖和关联的含义和区别。
2) 《设计模式》中文版第15~16页,Gof讲述了聚合和相识的差别。
3) 《非程序员》第二期之《类之间设置成“关联”OR“依赖”似乎全在个人喜好》是几位朋友就聚合、关联和依赖的区别进行的很好的讨论。
至此,如果您对它们的定义持不同意见,又或者觉得难以理解的话,不妨把别人的那一套都抛开,自己把它们重定义一遍。反正我们又不是理论家,就算定义得不科学也没关系,只要和我们的项目有关的人员都一致的理解和接受就可以了。毕竟有效的沟通才是我们真正的目的。
实践建议
按需剪裁。项目要用到什么就学习什么,暂时不用的就放下。我们的目的是当前的系统建模,而不是一下子成为UML高手。
自由扩展。结合我们的实际情况,在使用的过程中,要明确UML的重点是“沟通”,其次才是“公共”。UML本身有许多规则和约定,但没必要一一遵守。只要有利于沟通的,我们就采用,否则就摒弃。通常我们的文档只是在小范围里传播,要统一理解并不困难。当然,当规则定好了后,最好就不要随意更改了。
一个远程对象至少要包括4个class文件:远程对象;远程对象的接口;实现远程接口的对象的stub;对象的skeleton这4个class文件。
在EJB中则至少要包括10个class:
Bean类,特定App Server的Bean实现类 Bean的remote接口,特定App Server的remote接口实现类,特定App Server的remote接口的实现类的stub类和skeleton类 Bean的home接口,特定App Server的home接口实现类,特定App Server的home接口的实现类的stub类和skeleton类
和RMI不同的是,EJB中这10个class真正需要用户编写的只有3个,分别是Bean类和它的remote接口,home接口,至于其它的7个class到底是怎么生成,被打包在什么地方,或者是否需要更多的类文件,会根据不同的App Server表现出比较大的差异,不能一概而论。
拿我最熟悉的Weblogic的来说吧,Weblogic的Bean实现类,以及两个接口的Weblogic的实现类是在ejbc的时候被打包到EJB的jar包里面的,这3个class文件可以看到。而home接口和remote接口的Weblogic的实现类的stub类和skeleton类是在EJB被部署到Weblogic的时候,由Weblogic动态生成stub类和Skeleton类的字节码,因此看不到这4个类文件。
对于一次客户端远程调用EJB,要经过两个远程对象的多次RMI循环。首先是通过JNDI查找Home接口,获得Home接口的实现类,这个过程其实相当复杂,首先是找到Home接口的Weblogic实现类,然后创建一个Home接口的Weblogic实现类的stub类的对象实例,将它序列化传送给客户端(注意stub类的实例是在第1次RMI循环中,由服务器动态发送给客户端的,因此不需要客户端保存Home接口的Weblogic实现类的stub类),最后客户端获得该stub类的对象实例(普通的RMI需要在客户端保存stub类,而EJB不需要,因为服务器会把stub类的对象实例发送给客户端)。
客户端拿到服务器给它的Home接口的Weblogic实现类的stub类对象实例以后,调用stub类的create方法,(在代码上就是home.create(),但是后台要做很多事情),于是经过第2次RMI循环,在服务器端,Home接口的Weblogic实现类的skeleton类收到stub类的调用信息后,由它再去调用Home接口的Weblogic实现类的create方法。
在服务端,Home接口的Weblogic实现类的create方法再去调用Bean类的Weblogic实现类的ejbCreate方法,在服务端创建或者分配一个EJB实例,然后将这个EJB实例的远程接口的Weblogic实现类的stub类对象实例序列化发送给客户端。
客户端收到remote接口的Weblogic实现类的stub类的对象实例,对该对象实例的方法调用(在客户端代码中实际上就是对remote接口的调用),将传送给服务器端remote接口的Weblogic实现类的skeleton类对象,而skeleton类对象再调用相应的remote接口的Weblogic实现类,然后remote接口的Weblogic实现类再去调用Bean类的Weblogic实现类,如此就完成一次EJB对象的远程调用。
看了一遍帖子,感觉还是没有说太清楚,既然写了帖子,就想彻底把它说清楚。
先拿普通RMI来说,有4个class,分别是远程对象,对象的接口,对象的stub类和skeleton类。而对象本身和对象的stub类同时都实现了接口类。而我们在客户端代码调用远程对象的时候,虽然在代码中操纵接口,实质上是在操纵stub类,例如:
接口类:Hello 远程对象:Hello_Server stub类:Hello_Stub skeleton类:Hello_Skeleton
客户端代码要这样写:
Hello h = new Hello_Stub(); h.getString();
我们不会这样写:
Hello_Stub h = new Hello_Stub(); h.getString();
因为使用接口适用性更广,就算更换了接口实现类,也不需要更改代码。因此客户端需要Hello.class和Hello_Stub.class这两个文件。但是对于EJB来说,就不需要Hello_Stub.class,因为服务器会发送给它,但是Hello.class文件客户端是省不了的,必须有。表面上我们的客户端代码在操纵Hello,但别忘记了Hello只是一个接口,抽象的,实质上是在操纵Hello_Stub。
拿Weblogic上的EJB举例子,10个class分别是:
Bean类:HelloBean (用户编写) Bean类的Weblogic实现类:HelloBean_Impl (EJBC生成) Home接口:HelloHome (用户编写) Home接口的Weblogic实现类 ((Hello Bean))_HomeImpl(EJBC生成) Home接口的Weblogic实现类的stub类 ((Hello Bean))_HomeImpl_WLStub(部署的时候动态生成字节码) Home接口的Weblogic实现类的skeleton类 ((Hello Bean))_HomeImpl_WLSkeleton(部署的时候动态生成字节码) Remote接口: Hello (用户编写) Remote接口的Weblogic实现类 ((Hello Bean))_EOImpl(EJBC生成) Remote接口的Weblogic实现类的stub类 ((Hello Bean))_EOImpl_WLStub(部署的时候动态生成字节码) Remote接口的Weblogic实现类的skeleton类 ((Hello Bean))_EOImpl_WLSkeleton(部署的时候动态生成字节码)
客户端只需要Hello.class和HelloHome.class这两个文件。
((Hello Home)) home = (Home) ((Portable Remote Object)).narrow(ctx.lookup("Hello"), ((Hello Home)).class);
这一行代码是从JNDI获得Home接口,但是请记住!接口是抽象的,那么home这个对象到底是什么类的对象实例呢?很简单,用toString()输出看一下就明白了,下面一行是输出结果:
((Hello Bean))_HomeImpl_WLStub@18c458
这表明home这个通过从服务器的JNDI树上查找获得的对象实际上是HelloBean_HomeImpl_WLStub类的一个实例。
接下来客户端代码:
Hello h = home.create()
同样Hello只是一个抽象的接口,那么h对象是什么东西呢?打印一下:
((Hello Bean))_EOImpl_WLStub@8fa0d1
原来是HelloBean_EOImpl_WLStub的一个对象实例。
用这个例子来简述一遍EJB调用过程:
首先客户端JNDI查询,服务端JNDI树上Hello这个名字实际上绑定的对象是HelloBean_HomeImpl_WLStub,所以服务端将创建HelloBean_HomeImpl_WLStub的一个对象实例,序列化返回给客户端。
于是客户端得到home对象,表面上是得到HelloHome接口的实例,实际上是进行了一次远程调用得到了HelloBean_HomeImpl_WLStub类的对象实例,别忘记了HelloBean_HomeImpl_WLStub也实现了HelloHome接口。
然后home.create()实质上就是HelloBean_HomeImpl_WLStub.create(),该方法将发送信息给HelloBean_HomeImpl_WLSkeleton,而HelloBean_HomeImpl_WLSkeleton接受到信息后,再去调用HelloBean_HomeImpl的create方法,至此完成第1次完整的RMI循环。
注意在这次RMI循环过程中,远程对象是HelloBean_HomeImpl,远程对象的接口是HelloHome,对象的stub是HelloBean_HomeImpl_WLStub,对象的skeleton是HelloBean_HomeImpl_WLSkeleton。
然后HelloBean_HomeImpl再去调用HelloBean_Impl的ejbCreate方法,而HelloBean_Impl的ejbCreate方法将负责创建或者分配一个Bean实例,并且创建一个HelloBean_EOImpl_WLStub的对象实例。
这一步比较有趣的是,在前一步RMI循环中,远程对象HelloBean_HomeImpl在客户端有一个代理类HelloBean_HomeImpl_WLStub,但在这一步,HelloBean_HomeImpl自己却充当了HelloBean_Impl的代理类,只不过HelloBean_HomeImpl不在客户端,而是在服务端,因此不进行RMI。
然后HelloBean_EOImpl_WLStub的对象实例序列化返回给客户端,这一步也很有趣,上次RMI过程,主角是HelloBean_HomeImpl和它的代理类HelloBean_HomeImpl_WLStub,但这这一次换成了HelloBean_EOImpl和它的代理类HelloBean_EOImpl_WLStub来玩了。
Hello h = home.create();h.helloWorld();
假设Hello接口有一个helloWorld远程方法,那么表面上是在调用Hello接口的helloWorld方法,实际上是在调用HelloBean_EOImpl_WLStub的helloWorld方法。
然后HelloBean_EOImpl_WLStub的helloWorld方法将发送信息给服务器上的HelloBean_EOImpl_WLSkeleton,而HelloBean_EOImpl_WLSkeleton收到信息以后,再去调用HelloBean_EOImpl的helloWorld方法。至此,完成第2次完整的RMI循环过程。
在刚才HelloBean_EOImpl是作为远程对象被调用的,它的代理类是HelloBean_EOImpl_WLStub,但现在HelloBean_EOImpl要作为HelloBean_Impl的代理类了。现在HelloBean_EOImpl去调用HelloBean_Impl的helloWorld方法。注意!HelloBean_Impl继承了HelloBean,而HelloBean中的helloWorld方法是我们亲自编写的代码,现在终于调用到了我们编写的代码了!
至此,一次EJB调用过程终于完成。在整个过程中,服务端主要要调用的类是HelloBean_Impl, Hello Bean?_HomeImpl,HelloBean_HomeImpl_WLSkeleton,HelloBean_EOImpl,HelloBean_EOImpl_WLSkeleton。客户端主要调用的类是HelloBean_HomeImpl_WLStub,HelloBean_EOImpl_WLStub,这两个类在客户端代码中并不会直接出现,出现在代码中的类是他们的接口HelloHome和Hello,因此客户端需要这两个接口文件,而Stub是服务器传送给他们的。
可以在main()方法中加入以下语句直接生成DDL文件:
public static void main(String[] args) throws Exception {
Configuration cfg = new Configuration()
.addClass(Record.class);
SessionFactory sf = cfg.buildSessionFactory();
new SchemaExport(cfg).create(true, true);
... ....
}
如果要生成多个java类的DDL可以修改
Configuration cfg = new Configuration()
.addClass(Record.class)
.addClass(XXX.class)
.addClass(YYY.class);
经过了接近一天的时间,终于用Ant+Hibernate SchemaExport工具生成了DDL文件,需要注意将生成的Class文件加入到ClassPath中,
文件内容如下,这里指定生成的文件为ParentChild.hbm.xml,如果多个文件可以写成*.hbm.xml:
<!-- Copyright (c) 2002 by ObjectLearn. All Rights Reserved. -->
<project name="HibernateS" default="schemaexport" basedir=".">
<property name="base.dir" value="." />
<property name="src.dir" value="./src" />
<property name="lib.dir" value="./lib/" />
<property name="classes.dir" value="./classes/" />
<property name="build.dir" value="." />
<path id="myclasspath">
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
</path>
<target name="schemaexport">
<taskdef name="schemaexport"
classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
classpathref="myclasspath"/>
<schemaexport
properties="hibernate.properties"
quiet="no"
text="true"
drop="no"
delimiter=";"
output="${build.dir}/schema-export.sql">
<fileset dir="${classes.dir}">
<include name="**/ParentChild.hbm.xml"/>
</fileset>
</schemaexport>
</target>
</project>
既然在eclipse环境下工作,最好所有相关的工作都能在它里面完成。hibernate自带了一个工具schemaExport,可以让你从map文件产生数据库的ddl。
在eclipse project中新建一个目录,schemae,然后新建两个文件。schemae.xml,schema.properties。
schema.properties的内容:
neededClassPath=c:/hibernate-2.1.4/hibernate-2.1/hibernate2.jar;c:/hibernate-2.1.4/hibernate-2.1/lib/dom4j-1.4.jar;c:/hibernate-2.1.4/hibernate-2.1/lib/commons-collections-2.1.jar;c:/hibernate-2.1.4/hibernate-2.1/lib/commons-logging-1.0.3.jar;c:/hsqldb_1_7_1/hsqldb/lib/hsqldb.jar;D:/eclipse-SDK-2.1.2-win32/eclipse/workspace/fhjsj/bin/;D:/eclipse-SDK-2.1.2-win32/eclipse/workspace/fhjsj/fhjsj/WEB-INF/classes/
schema.xml的内容:
classpath="${neededClassPath}"/>
text="true"
drop="no"
delimiter=";"
output="workspace/fhjsj/schemae/schema-export.sql">
其它的hibernate.properties和mapping 文件根据web程序的规范放在WEB-INF的classes里面。最后产生的ddl文件放在schemae目录下面。
- Hibernate 的创始人 Gavin King 为编写 Hibernate 的文档(以及框架!)设置了很高的标准。要了解 Java 平台上这种让人激动的 OR 映射工具的更多内容,请参阅 Hibernate 主页上的 Hibernate User Guide。
- 可以通过 Rod Johnson 的 “Introducing the Spring framework”(ServerSide.com,2003 年 10 月),直接跟 Spring 的发明者学习有关它的更多内容。
- Matt Raible 的 AppFuse 很实用地介绍了对几个 J2EE 应用服务器上的数据库使用 Hibernate 和 Spring 。
- Juergen Hoeller 通过其教程“Hibernate -- Data Access with a Spring Framework”(2003 年 7 月)制定了关于 Hibernate、AOP、Spring 和事务管理的规则。
- 还想了解 Spring 的更多内容?请访问 Spring 主页。
- DbUnit 主页 是学习 Junit 这个方便扩展的第一资源。
- 有关 DbUnit 的介绍,请参阅 Philippe Girolami 的“Control your test-environment with DbUnit and Anthill”(developerWorks,2004 年 4 月)。
- 当然,Andrew Glover 的“ Effective unit testing with DbUnit”(OnJava.com,2004 年 1 月)是另一个很好的资源。
- 通过 Malcolm Davis 的“利用 Ant 和 JUnit 进行增量开发”(developerWorks,2000 年 11 月)学习更多有关 DbUnit 及其前身 JUnit 的内容。
- 至于 AOP,它是由 Nicholas Lesiecki 一手开创的(至少我认为是这样)。阅读他的“使用面向 Aspect 的编程改进模块性”(developerWorks,2002 年 1 月),了解这种强大的面向对象编程的补充技术。
- 在“AOP 解决紧密耦合的难题”(developerWorks,2004 年 2 月)中,Andrew Glover 又回来了,这一次他展示了如何用静态横切来解耦企业应用程序。
- Sean Sullivan 的“高级 DAO 编程”(developerWorks,2003 年 10 月)提供了对 DAO 模式的简要介绍,然后直接进入如何在 J2EE 编程中发挥它的强大功能。
- 对于那些刚开始接触 CMP CMR 的人,Richard Hightower 编写了一个关于这个主题的全系列教程,首先是“Introduction to container-managed persistence and relationships”(developerWorks,2003 年 3 月)。
- 有关 XDoclet 的简要教程,请参阅也是由 Richard Hightower 所写的“Enhance J2EE component reuse with XDoclet”(developerWorks,2003 年 5 月)。
- 当然,还可以查看 SourceForge 上的 Hibernate XDoclet 标签。
- 在 developerWorks Java 技术专区 中可以找到关于 Java 编程各个方面的文章。
- 访问 Developer Bookstore ,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。
- 还可以参阅 Java 技术专区教程页,获得 developerWorks 免费 Java 教程的完整清单。











