曾几何时,EJB被人们当做J2EE的核心而顶礼膜拜。可惜,过去七年的经验褪去了EJB的光环。我现在更多地把EJB当作一种过渡性的技术:它普及了很多有价值的思想;但对于大多数新的应用来说,它并不是最佳的选择。在本文中,我们将审视EJB 教给我们的东西,以及——更重要的是——如何享受那些有价值的思想,同时避开EJB的重大缺陷。
EJB的原意是简化企业应用的开发,它希望让应用开发人员将能够将注意力集中于他们的问题领域和编写业务逻辑,而不是去关注系统级的问题。同时,EJB规范也承诺EJB(以及基于EJB的应用)在不同应用服务器之间的可移植性。那么,这些期望达到了吗?
一个过时的组件模型
没有什么可惊奇的,EJB现在看起来已经锈迹斑斑了。1998年3月,当EJB第一次出现时,企业软件的世界完全是另一番风景。自那以后,发生了如此之多的变化,以至于我们很难确切地回忆起当时的行业状况。当EJB规范1.0版问世时,在企业软件领域根本没有标准。微软事务服务器(Microsoft Transaction Server,MTS)可能是最接近一个企业组件框架的东西,但它是厂商专有的,而且依赖于微软对C++和其它语言并不优雅的扩展,并且紧密绑定在 COM/DCOM上。
CORBA是COM/DCOM之外唯一开放的替代品。然而CORBA尽管功能强大,用起来实在太复杂,对C++之类语言的依赖也令人望而生畏。而且CORBA本质上是用于分布式组件通信的,而不是管理应用对象。CORBA的ORB也不是真正的应用服务器。
在这样的环境中,EJB看起来既简单又开放。然而,随后几年的技术发展,使得EJB不再像当初那样具有独到的优势。下面我将介绍这些重要的技术发展。
与今天大不相同的不仅是技术背景:当时的商业环境也要温和得多。电子商务(.com)领域能够向基础架构投入大把的钞票,即便它们暂且无法证明自己的效益也在所不惜。尽管这个良性的经济气候催生了很多有价值的技术(例如J2EE),但它也造成了负面的影响:人们对于新技术的质疑不够。而EJB可能正是这种情况最大的受益者:这样一种涉及应用服务器许可证、硬件性能、开发时间、培训的成本高昂的技术,原本不应该在短时间内获得广泛接受的。
Java语言的进步
EJB 1.0规范是在Java 1.2对API进行重大改进之前6个月发布的。想想那时候的Java吧:新的集合框架还未出现;Swing还不是Java核心的一部分,还被放在 com.sun.java.swing这个包里;Javadoc还不能使用frame;类加载器(class loader)还不是自然分为多个层次的。你还记得那是怎样的Java吗?
J2SE 1.3又对Java进行了更为重大的改进,例如动态代理(dynamic proxy),它使得任何接口可以被一个运行时生成的代理所实现。有了这样的能力,EJB所采用的那种“不完全匹配组件接口”的实现方式看起来越发的笨拙。要求一个专门的部署阶段也同样值得怀疑:实际上,很多EJB容器厂商很快利用动态代理的先进技术取消了代码生成和编译的步骤,而这正是EJB部署过程中的重要一环。看起来,大多数厂商都同意:session bean不再需要代码生成的支持,只有entity bean才有必要。[1]
.NET的挑战
EJB最初的灵感部分来自于MTS,后者则借鉴了更老的“事务监视器”的概念。不过,EJB的吸引力比MTS可要大得多。在EJB的大部分时间里,微软缺乏一个可信任的企业框架。COM/DCOM和MTS都无法令人信服,EJB 在这个领域基本上无人竞争。
2002年初,随着雄心勃勃的微软.NET企业平台的发布,情况发生了改变。.NET受J2EE的影响颇深,微软从J2EE专家这里汲取了大量知识。同时,.NET又与J2EE有一些显著的差异,特别值得一提的是与EJB的差异。(我在这里只关心体系架构方面的差异。很显然,与J2EE不同,. NET实际上是一种专有的系统,但这并不是我们现在要讨论的。)
很多Java开发人员错误地认为:EJB核心服务在.NET中没有等价物。实际上,尽管.NET没有EJB的直接等价物,但它却提供了类似于EJB的关键服务。
.NET模糊了web容器与EJB容器之间的区别,而区分这两种容器恰好是“经典”J2EE架构的根本。任何一种.NET语言中的对象都可以通过继承System.EnterpriseServices.ServicedComponent来使用企业级服务,这里没有独立于受控环境的特殊“容器”。与其说这是一个缺陷,倒不如说是一种改进:这意味着任何对象都可以享受企业级服务,而不需要特殊的部署阶段,也无须在开发时承担那么沉重的包袱。说实话,我并不欣赏“强制继承ServicedComponent”的做法,但那也好过实现一个EJB。
部署这样一个“享受企业级服务的组件”比部署EJB简单多了,因为.NET也抛弃了J2EE中的很多外部部署描述文件。譬如说,对于那些用于声明性事务管理的元数据,.NET不是将它们放到独立的XML部署描述文件(例如ejb-jar.xml文件)中,而是“元数据属性”的形式存放在实际的组件源程序中。下面的例子定义了一个事务性的对象,它的方法将访问一个隐含的事务上下文,后者会提供类似于EJBContext的事务管理方法。请注意,这个类必须扩展ServicedComponent:
[Transaction(TransactionOption.Required)]
public class MyComponent :ServicedComponent, MyInterface {
public void myMethod(long objectIdentifier) {
...
}
...
}
