EJB(2.X-3.0)、Hibernate、Spring:剖析、批判和展望
12/28/2004于珞珈山
一、 前言
我强调EJB、Hibernate、Spring的恩怨情仇,同时也必须说明,我一向反感你说我怎么侵入、你说我怎么依赖式的EJB VS Hibernate、EJB VS Spring的讨论,因为这种行为本身就是没有意义的、错误的。我提倡从正确的技术对比和理性的技术批判中受益。对比,我们需要找准对比点;批判,我们需要从source、spec、application context中分析、批判。
二、 从EJB说起
2.1 EJB几种Bean类型的引入顺序
EJB1.0,有两种Bean类型:SessionBean、EntityBean。
EJB2.0,引入CMP EntityBean、引入Message-Driven Bean、引入Local接口。
2.2 Entity Bean和O/R Mapping的微妙关系
我想对O/R Mapping、O/R Mapping Engine做一个简要的说明。
O/R Mapping,以对象视图(Object View)来看待DB Record,对象操作能够通明地映射成DB Record操作。
O/R Mapping Engine,就是使得O/R Mapping成为可能的具体实现手法。
从我们的定义来看,使用BMP EntityBean意味着你自己在实施一个非常简单的O/R Mapping ,你自己在为能够以对象视图和DB交互做出努力。而为了支持CMP EntityBean,EJB Server提供商会为你提供O/R Mapping 能力。而且,事实的确是这样,任何支持CMP EntityBean的EJB Server都需要提供一个Persistence(O/R Mapping) Engine,譬如JBOSS的JAWS(Just Another Web Store)。
至于,Hibernate、IBATIS等,虽然,也叫做O/R Mapping Tool,但是它们的意义已经远远超过了CMP EntityBean O/R Mapping Engine这样的Tool。下面会有详细的分析。
2.3 EJB-1.0
EJB1.0是分布式组件架构,包括SessionBean和EntityBean。它引入了很多非常前卫的技术、概念。主要包括分布式组件、容器、DB操作的对象试图。
EJB1.0,可能Expert Group设想的EJB的应用场景是大规模分布式的系统。所以,SessionBean、EntityBean尚没有引入Local接口。 client->SessionBean、client->EntityBean、SessionBean->SessionBean、SessionBean->EntityBean等等都是remote的。
EJB1.0,将容器这个概念引入EJB,意义重大。这里我想顺便澄清一个问题:容器是什么?我的观点:容器只是一个概念、一种架构。就拿EJB Server来说,Server试图为Bean提供分布式、事务、安全等基础设施,那么就必须有一个凌驾于Bean之上的Layer或者说warp,这样才能够从高层拦截Bean调用,进行一些额外操作。这样的架构就叫做容器架构,这个概念当然不是自EJB才有的。至于怎样实现,方法各异。
EJB1.0为DB操作提供了对象试图。Expert Group当初是怎样定位EntityBean的?无疑,1.0中的EntityBean,也就是2.0以后的BMP EntityBean,定位是Domain Object(我不知道当时有没有这个概念,只是它们的思想是非常一致)。它的fields直接映射DB Table Schema,member functions就是对Table Record的操作。Client->EntityBean、SessionBean->EntityBean等就可以直接和数据库交互了。
有人跟我说EJB1.0基于Catalysis方法学,SessionBean对应Role,EntityBean对应Domain Object。到目前为止,我对这种说法,持保留态度,因为EJB Spec中,我丝毫没有这种说法的痕迹。
2.4 EJB-2.X
无疑,EJB1.X的设计存在重大的缺陷。2.0增加的特性包括Local接口、CMP EntityBean,它们是对1.x缺陷的重大更正。
首先,事实上没有多少Expert Group想象中的大规模分布式应用。我们从两个方面来说:(1)通过Remote方式使用 EntityBean引起严重的性能问题,很有必要提供Local接口。(2)访问SessionBean的WebApplication和SessionBean部署在同一服务器上的情况非常普遍,所以提供SessionBean的Local接口,也是必然的事情。2.X之后,最常用的一个Pattern就是使用SessionBean Façade通过Local的形式访问EntityBean。而且,即使应用规模大到连SessionBean和EntityBean都需要部署到不同的Server,也没有关系,因为EJB2.X同样支持Remote接口。
其次,EJB2.0引入CMP EntityBean。CMP EntityBean解决了EntityBean持久化表示和JDBC分离的问题,同时大大简化了EntityBean的开发、提高了性能。但是,我不得不说,CMP EntityBean将EJB1.X的Domain Model理念完全冲掉了,因为CMP EntityBean是不能包含任何Domain Logic的。BMP EntityBean似乎就是Matrin所说的DomainObject,而CMP EntityBean在典型的SessionBean->EntityBean这样的应用场景下,似乎就是Martin所说的Transaction Script中的AnaemicDomainObject。
三、 我理解的Hibernate
本来,本文的题目叫做《EJB、Spring:剖析、批判和展望》,因为我觉的Hibernate和EJB、Spring不是一个层次的东西,虽然,这个道理很浅显,但是为什么那么多人还拿Hibernate来攻击EJB,来攻击EntityBean?EntityBean是值得狠狠攻击的,但是你用错了枪。
我上面提到,支持CMP EntityBean的EJB Implements都有一个Persistence Engine,也就是O/R Mapping Engine。CMP O/R Mapping Engine用来做什么的?它通过分析CMP Abastract Schema、分析EJBQL、分析Bean状态等行为,生成SQL,然后和DB 进行交互。
而在我眼里,Hibernate不是”O/R Mapping Tool”这几个字能概括的了。我说Hibernate是一款独当一面的轻量级翻译中间件,是Layer,和CMP EntityBean O/R Mapping Engine不是一个层次的东西了。
Application------->CMP EntityBean Operation-------->DB |
|
在这里,我建议你停下来,想想EntityBean是不是应该对应Hibernate中的PO/POJO?举个例子,你修改PO后,有时是不是需要sessionObj.update(po)来更新(当然,在非夸Session的情况下,不需要显式的update的),这个sessionObj.update(po)是不是表示你直接使用Hibernate的Persitence Engine?是的。而在EntityBean中,你修改EntityBean后,你需要其它的行为来使得EntityBean的变化同步到DB吗?不需要。因为,EJB Container拦截你的调用,在你更改Bean的field之前、之后,container会调用load/store方法的(当然,在BMP/CMP EntityBean中,情况是不同的,BMP EntityBean调用programmer自己用JDBC编写的load/store等方法,而CMP EntityBean,使用CMP Peristence Engine来做这个工作)。这样,就隐式的持久化数据了。不需要,你像Hibernate那样调用session.update这样的语句。EntityBean这种同步方式是对它性能差的重要原因之一。值得注意的是,EJB Implements对于EntityBean同步并不完全是我上面描述的那样,同步的频率和事务、特定的implements是紧密相关的。
|
总的来说,CMP EntityBean O/R Mapping Engine是为静态的、功能固定的EntityBean的O/R Mapping提供支持而开发的。而Hibernate担任的是一个Layer的作用。
四、 Spring不是神话
Rd Johnson聪明在哪里?聪明在,他坚持了自己的实践,而不是随大流。Rd Johnson认识到90%的应用不需要分布式、不需要J2EE中那些重量级的技术,譬如JNDI,他就动手为EJB脱去Remote这层皮、将大多数应用中不必要的技术隔离、改造。从适用范围上来说,Spring对EJB做了90%的补充。
个人看法:Spring的哲学在于,framework针对最常见、最简单的应用场景而设计,等到需要特殊技术的时候,再想办法解决问题。这样,在绝大多数没有特殊要求的应用中,Spring就显示出优势来了。下面,我们会做详细的讲解。
4.1 Spring“无侵入性“是谎言,但是有资格笑”百步之外的EJB”
“无侵入性”是Spring标榜的特性。但是,我想说,Spring的“无侵入”是谎言,随着应用的深入,“无侵入”对什么framework来说,都是个神化。
什么就叫“无侵入性”?部署到Spring中的Object不需要强制任何实现接口就可以说Spring是“无侵入性”的?我觉的,这是大错特错。如果你非要说,Spring的确不需要像EJB那样强制实现一些接口,那么我只能告诉你:
(1)Spring设想的Object的应用场景是从最简单的出发。所以,它没有,为了一些预料中要使用的特性而强制Object实现一些特定的接口。但是,事实上,在Spring中,如果你的应用场景稍微深入一点,那么你就和和Spring绑定了。
(2)Spring管理的Object,从某种意义上说是没有状态的。
针对第一点,我举两个个例子。(1)EJB内部方法的调用,会导致基础设施不会起作用。但是Bean接口(SessionBean、EntityBean、MessageDrivenBean)中都有可以使Bean获得自己Context的支持,譬如:SessionBean的setSessionContext(SessionContext ctx) 等等,容器部署Bean的时候会通过它给每个Bean设置一个上下文。而EJBContext中,有EJBObject getEJBObject这样的函数,可以使得Bean获得自身的EJBObject,这样通过EJBObject来调用Bean自己的函数,基础设施就会起作用了。而Spring中,如果,一个Object的函数需要调用自己的其它函数,而又希望譬如安全检查、事务等等Aspect起作用?那么Spring,怎么做?你需要设置Bean的exposeProxy属性。
ExposeProxy: whether the current proxy should be exposed in a ThreadLocal so that it can be accessed by the target. (It‘s available via the MethodInvocation without the need for a ThreadLocal.) If a target needs to obtain the proxy and exposeProxy is true, the target can use the AopContext.currentProxy() method.
|
所以,当你需要上面说的内部调用需要基础设施起作用的特性,不管在EJB还是Spring肯定需要和特定框架绑定的。为什么说,Spring五十步笑百步?因为,我上面提到,Spring在Object很简单的情况下,是可以任意部署的、复用的。而EJB却不管你需不需要,开始就设想你需要的。同样,Spring中的BeanFactoryAware、BeanNameAware等等接口也都说明了一点:Spring将特性从Object剥离,从而,尽量降低它的依赖性。只有当你的Object复杂的时候,framework才会侵入你的Object。
针对,第二点,我想着重谈一下。为什么说,从某种意义上说Spring中部署的对象是没有状态的?我们知道,Spring支持两种Object:Singleton和Prototype。Spring Spec中,认为,Singleton可以称为stateless的,Prototype可以称为是statefule的。而在EJB的世界中,StatefuleSessionBean和EntityBean也称作是stateful的。那么,它们的stateful分别意味着什么?它们为什么在依赖性方面有那么大的区别?为什么Spring中的Object不需要实现特定接口,而EJB需要?先来,看看EJB的SessionBean接口:
|
|
|
|
|
|
|
|
其中的setSessionContext我上面说过。看ejbActivate()、ejbPassive(),为什么会有这两个函数?而Spring不需要实现有同样函数的接口?这是EJB和Spring的对象管理机制的不同造成。EJB implements一般来说,为了复用Bean,会采用一级Cache加上一级InstancePool(StatelessSessionBean是不需要Cache的),从而支持将StatefulSessionBean持久化到磁盘,支持EntityBean的Bean Instance(注意这个Bean Instance和client得到的EntityBean是不同的,它没有和任何的DB Record关联)的复用,这就导致了ejbAcrivate、ejbPassivate等的引入。但是,Spring没有采用这样管理机制,它只有Singleton/Prototype。而Prototype虽然也可以说成是Statefule的,但是它不会在不同的client中复用Object Instance,而是每一个client一个对象,哪怕一万个client,那么就产生一万个Instance,而在EJB中,可能使用100 Instance来服务,将not active的Bean持久化到磁盘,复用Bean Instance。还请注意,这里我不是说EJB中的StatefuleSessionBean好,事实上我发现,一般来说,当并发量很大时,采用节约内存而持久化Bean到磁盘这种策略,I/O瓶颈引起的问题更为严重。
再看,ejbRemove,这个没什么多说的,Spring中你可以选择实现InitializingBean、DisposableBean接口,但是Spring推荐不要这样做,可以写普通的init成员函数,然后在配置文件中指明init-method、destroy-method属性,这样避免和Spring框架的绑定。
总的来说,Spring从对象最基本的引用场景出发,当需要复杂特性的时候,才会采用特殊机制来解决问题,也就是在这时,才会使应用绑定到Spring中。所以,它的侵入性比较低,但是不是“无侵入性”,不是你想的那么美好,当然,也没有“绝对无侵入“的framework。
4.2 我觉的Spring IOC的设计思路不够完美
Spring的IOC被一些人当作多么神奇的东西。
EJB具有Spring中所说的那种IOC的能力吗?答案是肯定的。EJB中的EJB引用、资源引用、环境属性都可以说是IOC,不是吗?然而,Spring和EJB的IOC不同在哪里?
Spring注入的特色:主要考虑Local Object的查找,这个时候不需要任何的协议(譬如JNDI),当你需要注入Remote Object的时候,采用RMI协议或者使用第三方Tool(譬如Hessian)。
EJB的特色:无论你的Bean-Bean是否部署在同一台机器上、Client->Bean是否在同一台机器上,肯定需要通过JNDI来查询Bean,只是,如果是它们在同一台机器上的时候,你使用Local接口,这样使得调用变为Local调用,从而提升性能。EJB它从出生时起,就定位为分布式组件架构,一头栽进“distributed”不容易出来了。这个可能就叫“尾大不掉”吧。
这一切的不同,只能说,它们的定位不同。一个更关注Local、一个更关注Remote。Spring仍然坚持它的哲学,从最基本的、大多数的场景考虑起,到特殊需要的时候,再想办法来解决问题。它试图找到J2EE开发和系统能力的均衡点。
可以说,Spring的做法,更加合情合理。但是,我也相信,Spring在”只是为Remote注入提供简单的支持“这一点上有点矫枉过正。我觉的,它可以做的更好,譬如通过作为J2EE标准的JNDI来封装Local、Remote查找。
目前,Spring不怎么关心Remote Object注入,对于需要Remote注入的情况,只提供简单的支持,而且还需要针对expert单独写配置信息。在这里,EJB3.0的做法,我觉的,是目前,最方便、最理智、也是最有前途的。EJB3.0通过@remote、@local就可以让EJB Server做不同的部署。
Spring导出远程对象。
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- does not necessarily have to be the same name as the bean to be exported -->
<property name="serviceName"><value>AccountService</value></property>
<property name="service"><ref bean="accountService"/></property>
<property name="serviceInterface"><value>example.AccountService</value></property>
<!-- defaults to 1099 -->
<property name="registryPort"><value>1199</value></property>
</bean>
Spring中注入Remote是怎样做的?
<bean class="example.SimpleObject">
<property name="accountService"><ref bean="accountService"/></bean>
</bean>
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl"><value>rmi://HOST:1199/AccountService</value></property>
<property name="serviceInterface"><value>example.AccountService</value></property>
</bean>
看了,这段代码,你就知道了。
这种方法非常的轻量级,从本质上来说,这种方法和JNDI没有任何的不同,这种方法,也需要一个NamingService,还记得RMI编程中,运行服务端的时候,首先运行rmiregistry,这个实际上就是非常简单的NamingService。看来,Rd Johnson对JNDI真是深恶痛绝啊。怎么也不愿意用JNDI。Spring这种方法,也许没有JNDI那样重量级,但是很显然它不能支持工业级分布系统,J2EE发展到今天,JNDI已经成为最核心的技术,它不是简单的Object Naming Service,它提供标准接口来定位用户、微机、网络、对象、服务器等等。它已经广泛而深入的进入J2EE技术的各个领域、成为J2EE系统的纽带。
举个很简单的例子:我需要在Spring应用中动态获取Remote Object,我该怎么做?我需要无缝使用LDAP,我该怎么做?答案是,Spring不能帮到你什么。
那么我就想,Spring为什么不使用JNDI来封装Local、Remote查找两种协议?从而,使用J2EE标准来实现无缝“注入”。这样带来的优点就是:(1)适应JNDI已经广泛而深入渗透的领域(2)JNDI是J2EE标准(3)支持动态Remote Service获取(4)支持工业级分布系统。
Properties props = new Properties();
pros.put(“searchServiceFactory”,”org.net.spring.bean.BeanFactory”);
Context ctx = new Context(env);
ctx.lookup(“servicename”);
Properties props = new Properties();
pros.put(“searchServiceFactory”,”com.sun.j2ee.jndi”);
Context ctx = new Context(env);
ctx.lookup(“servicename”); |
从下面,你可以看出,我个人认为Spring如果仍能OS,free,也许还会有很多人选择它,但是如果,它试图商业化,那么可以肯定它必须向EJB3.0靠拢。向标准靠拢。
|
4.3 独立的、完整的、面向Programmers的AOP框架才是Spring真正的亮点
EJB具有Spring中所宣扬的AOP能力吗?答案是肯定的。EJB的基础设施就是使用AOP技术实现的。然而,我要说,EJB的AOP并没有面向Programmers。为什么这么说?EJB Implements为你提供一组基础设施(例如,JBOSS中那些可配置的interceptors)。你可以根据系统需要配置。但是,你不可以编写自己的interceptor,然后配置到系统中。因为EJB的interceptors往往和EJB Implements内部愈合很紧,你想编写自己的interceptor,意味着你必须阅读EJB Implements的source,了解它的实现。换句话说,EJB Implements中的AOP技术是为了EJB Server能够提供基础设施而使用的,它没有想到为programmer提供更多的AOP能力。而Spring的AOP开始就是作为一个框架来发展的,是独立的、完整的。造成这种情况,是历史的原因。EJB Implements作者也不是神人,他们不可能,N年前,就想到将AOP框架设计的足够独立,从而面向programmes。
个人观点:EJB3.0在基础设施方面的说明,基本沿袭EJB2.X的。只是,可以提到EJB3.0支持通过annotations来使用基础设施,没有说,EJB3.0需要完善的AOP框架,但是,我想,EJB3.0 Implments应该都会提供一个独立的、完整的、面向programmer的AOP框架。事实上,JBOSS不早就有了J
|
当然,在Spring中使用AOP也不是那么的轻松,譬如,让你自己写TransactionProxy,你还是需要了解Spring AOP内部运作机制的。
4.4 Spring对其它框架的集成
这个问题,就不谈了。
五、EJB将走向何方
5.1 EJB-3.0
我们没必要说EJB2.X本身有多少的缺陷,毕竟,它是前一个J2EE时代的产物,只能说EJB2.X已经不能反映大多数J2EE应用的实际需要。过时了。那么EJB3.0打算带我们走向何方?
EJB3.0 Spec除了针对简化开发、方便测试、方便部署等目标做了不少的修改,更重要的是EJB3.0对SessionBean,特别是EntityBean模型做了一个全面的整容手术。这种修改是革命性的。
在我的《如果我来改进EJB2.X模型》中,我谈到,如果,让我对EJB2.X的EntityBean模型做修改,那么首先需要为新的模型定好位。就拿EntityBean来说好了。
第一条路:继续EntityBean设计的初试理念:Remote Domain Model(包括BMP EntityBean代表的Domain Model和CMP EntityBean代表的AnaemicDomainObject),并且保留Local接口,力图改经持久模型的设计,提高性能(即使CMP EntityBean的性能也是难以令人接受的,这种情况,我个人认为,主要是因为EntityBean模型设计的不好,在我的另一篇《如果我来改进EJB2.X模型》中有深入的分析)、增强功能(EJBQL实在太弱),让那些连SessionBean、EntityBean都需要部署在不同Server上的应用来为EJB2.X的EntityBean留口气。
但是,显然,EJB Server提供商是不可能甘心这一点羹的,因为那样的应用实在太少了。事实已经证明,如果EntityBean的Remote不是必须的,那么RemoteEntityBean性能上是不可行的,它只能工作在SessionBean后端,然而,即使EntityBean工作在SessionBean后端,但是EntityBean本身的局限性也太多,粒度要么太粗要么太细,性能、功能太弱,等等,开发数据应用非常地蹩脚,那么如果,在Remote EntityBean不是必须的情况下,我为什么不完全放弃EntityBean,在SessionBean后端使用其它的O/R Mapping Tool来开发数据应用,譬如Hibernate。这就是,EntityBean可以走第二条路。当然,从某种意义上来说,也是它必须走的路。
第二条路:完全抛弃EntityBean,采用Hibernate这样的O/R Mapping Engine作为Session Bean、Message-Driven Bean的后端数据持久化工具。而从EJB3.0可以看出,EJB3.0的确完全抛弃了传统的EntityBean模型。个人意见:可以这样说吧,EntityBean已经不复存在,Expert Group在SessionBean下给你换上了一个非常sharp的Persistence engine,你拿着engine,想干什么就干什么好了(上面讲过,EntityBean中,PersitenceEngine对client是通明的,这是由这两种引擎的本质作用决定的。有人说,EntityBean Application中不可以使用Dynamic Query,只能在配置文件中申明EJBQL,这些都是两种Persistence Engine的本质所决定的)。蹩脚的、强制模型的EntityBean不复存在!另外,EntityBean Remote特性在EJB3.0中根本没有提到,或许只是作为一个可选特性了吧(我还没有想到,EJB3.0中,如何来支持Remote PO,这个问题很诡异)。看来,Expert Group已经彻底否定了EntityBean的设计,或者说EntityBean的确是不符合实际需求的,Remote EntityBean、Remote Domain Object在绝大多数情况下是不切实际的。
话外题:Hibernate和JDO的关系,很微妙。EJB3.0和JDO的合并、Gavin进入EJB3.0 ExpertGroup令人很迷惑。EJB3.0的持久化模型采用JDO,应该是理所当然的。但是,目前,EJB3.0的Persitence Engine部分似乎被Hibernate左右,那么JDO的位置应该在哪里?
|
六、Spring将走向何方
无疑,“听起来很美妙的”IOC、实力、实用派Spring AOP、集成大量framework的Spring是目前、对分布式、高级J2EE特性要求不强的系统的最合理选择。但是,你可以看到,Spring能做到的,除了集成大量framework这个特性外(当然这个永远不会被写进EJB Spec,但是如果EJB Server供应商想这样做,也是非常简单的事),EJB3.0也能做到,而且很多地方做的比Spring好很多,最重要的,EJB是标准,所以,很肯定的说,如果Spring OS、free,保持目前的姿态发展,仍然会成为开发人员不错的选择,然而,如果Spring试图商业化,我是Rd Johnson的话,我会向EJB3.0靠拢,摇身成为EJB3.0 Server提供商。
七、EJB3.0是J2EE商用framework的未来
大肆革新过的EJB3.0,是J2EE商用framework的将来。
|