`
noodleLei
  • 浏览: 15930 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

EJB、Spring:剖析、批判和展望

阅读更多
    应该说EJB中对于数据的持久化方面和hibernate的功能是一致的,但两者没法从全局上比较,两者的适合范围和功能区别实在太大

    一段时间以来,EJB、Hibernate、Spring的恩怨情仇,是J2EE的热门话题。EJB VS Hibernate、EJB VS Spring这样的议题随处可在。这篇文章,笔者试图通过对技术发展史的回顾,对source的剖析、对比,深入挖掘这些技术出现的初衷、缺陷、走向。

前言

    我强调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才有的。至于怎样实现,方法各异。

    事实上,以个人的观点,容器架构的核心,不在于从高层“掌握”Beans(Objects),而在于采用怎样的技术来实现Bean(Object)调用的拦截。无疑,EJB Servers和Spring的实现手法是不同的。下面会详细讨论这个问题。

    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。

    顺便说一下,我个人是不同意Martin的RichDomainObject的说法。因为,在数据应用系统中,Martin提到的相对于Transacton Script中AnaemicDomainObject的RichDomainModel往往没有反映现实世界。一个Bank Account反映到现实世界,就是账本中的一条记录,它没有自发的动作,譬如withdraw。它和Person不同,Person可以有say(String words)这样的自发动作。Account的withdraw应该放到AccountManager中,由AccountManger来操作Account。不是说OO中的Object都需要有动作,现实世界中,本来就有静态的、没有自发动作的事物,譬如一个账本、一个帐号、一个资料库。虽然,不可否认,Rich Domain Model(对比AnaemicDomainObject说的)能够带来不少的好处(什么样的好处,你看看Martin的《Domain Logic and SQL》,就知道了)。

三、我理解的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
|
(O/R Mapping Engine)



----------------------



|---HQL、Criteria Query
Application------> Hibernate ------> |---POJO/PO Operation---------> DB
|---and so on



通过上面的两个图,你看出区别来了吗?
    EntityBean应用,不知道O/R Mapping Engine的存在,只需要使用EntityBean来完成交 互。
    而在Hibernate应用中,Application是直接使用Hibernate的。也就是说,它是直接使用O/R Mapping Engine的。
在这里,我建议你停下来,想想EntityBean是不是应该对应Hibernate中的PO/POJO?举个例子,你修改PO后,是不是需要sessionObj.update(po)来更新,这个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接口:
void ejbActivate()
The activate method is called when the instance is activated from its
"passive" state.
void ejbPassivate()
The passivate method is called before the instance enters the
"passive" state.
void ejbRemove()
A container invokes this method before it ends the life of the
session object.
void setSessionContext(SessionContext ctx)
Set the associated session context.



    其中的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导出远程对象。
AccountService

example.AccountService

1199
Spring中注入Remote是怎样做的?
rmi://HOST:1199/AccountService
example.AccountService

看了,这段代码,你就知道了。
    这种方法非常的轻量级,从本质上来说,这种方法和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标准来实现无缝。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics