2005年11月30日

作者/Rod Johnson             改写/透明

曾几何时,EJB被人们当做J2EE的核心而顶礼膜拜。可惜,过去七年的经验褪去了EJB的光环。我现在更多地把EJB当作一种过渡性的技术:它普及了很多有价值的思想;但对于大多数新的应用来说,它并不是最佳的选择。在本文中,我们将审视EJB教给我们的东西,以及——更重要的是——如何享受那些有价值的思想,同时避开EJB的重大缺陷。

EJB的原意是简化企业应用的开发,它希望让应用开发人员将能够将注意力集中于他们的问题领域和编写业务逻辑,而不是去关注系统级的问题。同时,EJB规范也承诺EJB(以及基于EJB的应用)在不同应用服务器之间的可移植性。那么,这些期望达到了吗?

一个过时的组件模型

没有什么可惊奇的,EJB现在看起来已经锈迹斑斑了。19983月,当EJB第一次出现时,企业软件的世界完全是另一番风景。自那以后,发生了如此之多的变化,以至于我们很难确切地回忆起当时的行业状况。当EJB规范1.0版问世时,在企业软件领域根本没有标准。微软事务服务器Microsoft Transaction ServerMTS)可能是最接近一个企业组件框架的东西,但它是厂商专有的,而且依赖于微软C++和其它语言并不优雅的扩展,并且紧密绑定在COM/DCOM上。

CORBACOM/DCOM之外唯一开放的替代品。然而CORBA尽管功能强大,用起来实在太复杂,对C++之类语言的依赖也令人望而生畏。而且CORBA本质上是用于分布式组件通信的,而不是管理应用对象。CORBAORB也不是真正的应用服务器。

在这样的环境中,EJB看起来既简单又开放。然而,随后几年的技术发展,使得EJB不再像当初那样具有独到的优势。下面我将介绍这些重要的技术发展。

与今天大不相同的不仅是技术背景:当时的商业环境也要温和得多。电子商务(.com)领域能够向基础架构投入大把的钞票,即便它们暂且无法证明自己的效益也在所不惜。尽管这个良性的经济气候催生了很多有价值的技术(例如J2EE),但它也造成了负面的影响:人们对于新技术的质疑不够。而EJB可能正是这种情况最大的受益者:这样一种涉及应用服务器许可证、硬件性能、开发时间、培训的成本高昂的技术,原本不应该在短时间内获得广泛接受的。

Java语言的进步

EJB 1.0规范是在Java 1.2API进行重大改进之前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/DCOMMTS都无法令人信服,EJB 在这个领域基本上无人竞争。

2002年初,随着雄心勃勃的微软.NET企业平台的发布,情况发生了改变。.NETJ2EE的影响颇深,微软从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) {

}

}

这个源码级元数据的用法很简捷。因为与EJB规范中的假设正相反,事务性行为通常是业务模型最基本的部分。“应用程序装配者”角色与“bean开发者”角色之间的划分仅仅存在于EJB规范委员会的头脑中,我从未在真实的项目中看到过这样的角色划分。甚至可以说,这种划分是非常危险的,因为改变事务语义就从根本上改变了行为,而这是任何单元测试都无法测出的。另外,代码重构也不会破坏源码级元数据,这正是外部XML部署描述文件的一个重大缺陷。

所有这些都值得认真对待。可悲的是,J2EE社区没有对.NET投入应有的关注。证据?如果有人傻到在TheServerside.com或是其它J2EE门户网站中发表与J2EE相关的.NET新闻,立即就会骂声四起——而且全都是毫无价值的老调重弹。幸运的是,J2EE社区中一些重要的推动者们显示了出一种开明的态度,他们愿意采用一些.NET所开创的、有价值的特性,例如:

Ø         Java 1.5将为Java添加源码级元数据,以及C#风格的“自动装箱”(autoboxing,简单数据类型和对象之间的转换)功能。

Ø         JBoss 4采用了.NET风格的元数据来驱动企业服务。

Ø         Spring和其它AOP框架(除了JBoss AOP之外)也使用了源码级元数据。

Ø         甚至EJB 3.0也有可能采用源码级元数据来描述目前保存在XML EJB部署描述文件中的信息。

Web Service

EJB 1.0EJB 1.1的年代,除了Java自己的RMI/RMP(那还是一种相当底层的技术)之外,重要的分布式对象技术就只有CORBACOM/DCOM了——这两种技术都很复杂,都没有被业界大量采用。相比之下,EJB更加简单、概念更加一致,因此它成功地在很多系统中引入了分布式对象——甚至是那些根本不需要对象分布的地方。

如今,情况已经大不相同了。基于XMLweb service提供了比过去大得多的开放性。这是真正的互操作,不仅仅是在微软技术和基于Java的解决方案之间,还可以在PerlTuxedo、以及其他很多语言和系统之间实现互操作。

同时,越来越多的人认识到:分布式对象的使用仅仅在少数应用中才是适合的。因此,快速增长的领域是在异构平台之间的互操作性,而不是J2EE应用服务器之间的远程调用。

EJB的另一个大问题在于:它总是试图把组件模型和远程支持放到同一个标准中,这样做有两个大问题:同时处理组件模型和远端支持增加了复杂性;而且远程协议还在不断发展。EJB规范最早3个版本希望将Java RMI(或者说,至少是RMI over IIOP)作为一个标准的远程协议,这种想法现在看起来非常可疑。随着更多远程协议的发展,EJB容器不可能支持所有的协议,而EJB又没有提供“可插入”的远程机制。

我喜欢的是一个更加模块化的方法:将“远程调用”看作一种服务,可以在对象模型之上通过façade将其暴露出来,就好像web界面就是业务对象之上的一个façade

敏捷方法学的兴起

最后,自从EJB1998年诞生之后,开发过程和最佳实践的思想发生了重大变化。可能最重要的事件就是“敏捷”方法学的兴起,例如极限编程(eXtreme ProgrammingXP)。

尤其值得一提的是,测试先行的开发test first development),或者至少是严格的单元测试,其价值在在很多项目中得到了证实。而EJB使得有效的单元测试非常困难,因为EJB严重依赖容器的服务。因此这就意味着EJB使得敏捷开发过程难以被应用。

从未出现的组件市场

我们对EJB的失望还在于它没能成功地创造一个第三方组件市场,而这是其承诺的主要愿景之一——还记得EJB规范所设想的、将多个厂商提供的EJB“组装”起来构成应用程序的情况吗?

这个组件市场并没有出现,它是否真的会出现也很值得怀疑。之所以这样,一部分是因为EJB的模型(它的可移植性有缺陷,部署也过于复杂),然而更重要的是:企业组件太过复杂,有太多的依赖关系需要包装。在实际工作中,那种所谓“可复用”的第三方EJB组件其实很少,而且我看到的几个例子也具有非常大的依赖性——例如,依赖一个精心设计的数据库schema和专有的数据库特性,或是依赖某种特定J2EE应用服务器的专有特性。这种依赖性使构想中的“J2EE组件市场”很成问题。过去也曾经有过成功的组件市场,例如ActiveX控件和(在一个较小范围内)JavaBean组件。然而这些组件所面对的问题要比企业级应用简单得多。

方兴未艾的新范式:AOP

最后不得不提的是,一种新的编程模型正在浮出水面。那是一种更为通用的解决方案,仅仅其中的一部分就能够提供EJB大多数有价值的能力,那就是面向方面的编程(Aspect Oriented ProgrammingAOP)。在J2EE应用开发中,我们主要用到AOP拦截interception)能力,它为我们提供了“在任何对象的方法调用前/后加入自定义行为”的能力,这使得我们可以处理企业应用中的横切crosscutting)关注点(即:同时作用于多个对象的关注点),并且仍然保持强类型(不需要改变方法签名)。例如,我们可以在一个应该具有事务的方法调用前开始一个事务,在方法返回时提交或者回滚。使用AOP让我们可以把“与事务管理相关的重复劳动”放进一个框架内。另外一个很适合使用AOP的场合则是自定义安全检查。

我把AOP看作是OOP的补充,而不是竞争对手。OOP在通常的场合下工作得很好,但在特定的领域里却有所欠缺:举例来说,如果我们必须为多个对象和方法应用相同的事务行为,我们需要将同样的代码剪切/粘贴到每一个方法里。AOP让我们可以把这类问题封装到方面aspect)中,从而更好地实现模块化。AOP定义了“切入点”(pointcut)的概念,让开发者可以从另一个角度来思考程序的结构,从而弥补了OOP的某些缺陷:如果需要对一组方法施加横切的行为,就应该拦截这些方法。

在这里,你可以看到与EJB的相似之处:EJB服务说到底就是拦截。客户端执行EJB上的一个方法,EJB容器则使用拦截来提供安全检查、线程管理、声明性事务管理等服务,这与AOP拦截是相同的概念。这是一个强大的概念, EJB最重要的价值都是由它提供的。

为了获得这些服务,AOPEJB更有吸引力,因为它对业务对象的要求更少。例如,它们通常不需要依赖一个特定的API(譬如EJB API);它们可以是POJO,从而使得开发更加容易,并且可以获得对象模型的所有利益。AOP的另一个优点是:它比EJB允许更大的开放性。譬如说,只要愿意,我们就可以自定义应用方面;而使用 EJB,我们就只能使用那些系统级的方面——这些方面是EJB规范规定、由EJB容器实现的。

EJB为我们提供了什么?

有经验的系统架构师倾向于只使用EJB的一小部分。毫无争议,无状态session beanStateless Session BeanSLSB)是EJB中最有用的,然后是message-driven bean(用于异步操作)。几乎同样毫无争议的,有状态session bean的价值是可疑的。EJB规范使得容器很难让有状态session bean具有与HTTP session对象同样的稳定性,这意味着在大多数场合HTTP session对象是更好的选择。entity bean可能是EJB规范中最虚弱的部分,它的性能相当差,并且没能解决好O/R映射中最重要的问题,而这恰好是人们希望它完成的工作。

为什么SLSB如此流行?因为它们是简单的组件(至少按照EJB标准是这样),并且对一些企业系统常见的问题提供了一个相当好的解决方案。下面,让我们逐一审视SLSB提供的主要服务:

声明性事务管理

SLSB最有价值的服务可能就是容器管理的事务Container-Managed TransactionCMT)了。尽管最终通过JTS/JTA协调事务的是J2EE服务器而不是EJB容器,但SLSB让我们可以使用声明性、而非编程性的事务管理。我们可以在Java代码之外告诉EJB容器如何划定事务边界。当然,如果想要回滚事务,我们需要使用EJB API,但是在理想的状态下中,我们不必为事务管理写哪怕一行代码,也不需要使用复杂的JTA API

尽管EJB CMT对于大多数应用来说很有价值,但要处理一些复杂的事务管理问题就显得力不从心了。譬如说,有时一个业务操作需要多个事务,如果每次事务都通过一个EJB入口点调用一个EJB方法,这就比通过编程方式界定事务更复杂了。乐观的锁定策略也会成为一个问题。如果我们使用声明性事务来驱动一种持久技术(例如TopLink)执行乐观事务,乐观并发异常将在容器提交事务之后才出现,而此时时应用代码已经将控制权交给了EJB容器,很难重新控制程序的运转。EJB没有为这类更复杂的场景提供良好的支持。如果使用Bean管理事务Bean-Managed TransactionBMT)的EJB,则需要从JNDI得到JTA UserTranscation对象,然后就像在应用服务器中运行的其他对象一样直接使用JTA API。在同一个EJB中混合BMTCMT也是不可能的,所以如果只有一个方法需要复杂的事务行为,所有的其它方法都必须承担BMTJTA的复杂性。

然而,尽管CMT不能解决所有的问题,对于大多数应用场景,它仍然不失为一种出色的技术,理应成为人们使用session bean的一个重要理由。

不过EJB CMT其实可以做得更好。EJB事务支持的另一个局限是:它与JTA驱动的全局容器事务绑在一起。尽管表面上看起来完全恰当,实际上这对于很多应用来说是杀鸡用牛刀。依赖全局JTA事务不仅带来了对EJB容器的依赖,同时还造成了对应用服务器所提供的分布式事务协调器的依赖,而后者仅仅在需要多个事务性资源(例如数据库)的时候才有意义。实际上,很多J2EE应用——甚至是那些非常复杂的应用——只需要使用一个数据库(可能是一个分布在集群上的数据库,例如Oracle 9i RAC),这些应用需要的仅仅是一个本地的、特定于资源的事务,当然声明性事务管理的好处总是存在的。

因此,可以同时适应高端和低端的轻量级、更少侵入性的事务基础架构才是真正的价值所在。例如Spring框架提供的基于AOP的声明性事务管理可以通过配置,在支持多个数据库的JTA、或者JDBC、或者其它的特定于资源的事务管理API(例如JDO事务API)之间切换,而不需要对业务代码做任何修改。这意味着你可以根据应用的需求选择最适合的事务管理策略——毕竟,如果你要做的只是一个单数据库应用,你就不需要具有JTA支持的高端的应用服务器。

而且,EJB CMT还可以做得更加灵活。只有当业务方法抛出一个非受控(unchecked)异常时,EJB容器才会回滚事务。非受控异常被看作是一个严重的问题,EJB容器会把它记入日志,然后销毁出错的EJB实例,并抛出一个EJBException(对本地客户端)或者RemoteException(对远程客户端)。如果抛出的是EJB规范定义的“应用”异常(RemoteException之外的受控异常),EJB开发者就必须调用EJBContext.setRollbackonly()方法指定回滚策略。这样的处理方式不是我们通常希望的,如果能够指定“哪些受控异常可以直接触发事务回滚”就更好了——这样的自动回滚将不被视为程序错误。Spring的声明性事务管理通过“回滚规则”提供了这样的能力,这样做的好处在于:应用开发者几乎不再需要调用一个特定于框架的setRollbackOnly()方法。

远程调用

远程调用也是EJB——尤其是SLSB——证实了自身价值的领域。直到EJB 2.0为止,SLSB只能提供RMI远程调用;EJB 2.1增加了web service远程调用。EJB容器支持远程请求的能力,就像支持EJB实例的生命周期一样,是很有价值的,任何一个还记得如何管理自定义的RMI服务器的人都会同意这一点。

然而,正如我曾经说过的,很多人认为EJB规范混淆了远程和组件模型。这里最重要的问题是:很多时候,我们根本不需要远程调用。EJB极大地简化了远程调用,也使它成为了一种危险的诱惑——诱惑人们采用一种并不适用、而且可能非常昂贵的体系结构(昂贵在复杂性、工作量和性能上)。

web应用中,将web组件和业务组件放在同一个JVM中几乎总是一个更好的主意。在这类应用中使用带远程接口的EJB无法增加任何价值,通常对于设计还是有害的。

集群

EJB常常被鼓吹为获得J2EE应用最大可伸缩性的不二法门。这种说法的理由之一是这样一种信念:为了获得比普通应用更高的可伸缩性,就必须采用分布式应用。另外一个信念是:EJB容器具有神奇的集群能力——商业EJB容器通常是昂贵的,所以期待其中存在某种魔力也是很合理的。事实上,EJB容器的集群功能并不是那么神奇,对于entity bean和有状态session bean的集群服务是相当有限的。

entity bean在集群中往往没有O/R映射解决方案(例如一个好的JDO实现)表现得那么好。在一个集群环境中,因为EJB容器的数据复制能力的局限,通常必须在每一个事务开始时从数据库加载entity状态。对比之下, Tangosol Coherence之类专门的集群缓存技术比应用服务器厂商所提供的复制功能更加强大。

有状态session bean的集群很容易引起问题,因为EJB规范本身就决定了它无法像Servlet API那样轻松地实现集群环境下的优化、减少节点间状态复制的次数。事实上,薄弱的集群能力已经被广泛认为是有状态session bean的致命弱点。

于是,关于EJB集群的争论最终归结到了远程无状态session bean。对于SLSB来说,它的所有实例都是相同的,并且可以互换。EJB容器只需要为远程客户端提供一个EJBObject存根,在其中用循环算法、基于负载的算法或者随机算法来提供负载均衡。但借助Cisco负载均衡管理器这样的设备,也可以在硬件级别很容易地实现负载均衡(而且很可能效率更高)。

问题的关键在于,集群的主要用途并不在于J2EE web应用的业务对象层。在下列位置的集群服务通常更为重要:

Ø         web 。通常必须在这里管理session状态,所以有必要设计一个路由方案。如果不需要保存session状态,一个web farm[2]风格的解决办法通常是最有效的。

Ø         数据访问层。

Ø         数据库。

水平可伸缩性的真正限制通常在于数据访问。大多数entity bean实现在集群环境要求每次对持久对象的访问都必须访问数据库,这会对性能造成影响,并且数据库负载也会升高(因为限制了可伸缩性)。如果采用一个专门的持久化解决方案(例如Hibernate或者一个JDO实现),再加上一个支持集群的缓存,我们通常可以获得更高的吞吐量。通常我们没必要在业务层管理状态,也就是说专门用于业务层的集群服务并不是很重要。

线程管理

EJB的线程管理所能提供的价值往往不像它所吹嘘的那么确切。EJB让开发者们能够以编写单线程程序的方式编写业务对象,同时又使这些业务对象能够适用于多线程环境,这当然是大家都愿意看到的。然而,实际情况是:EJB的线程支持无法彻底解决“对EJB facade之后的对象的并发访问”造成的问题,而且它也不是解决并发问题的唯一选择。另外,不论调用entity bean的任何方法,整个entity bean实例都会被加锁,这是一种相当幼稚的做法,而且并不总是很合理。

对于类似于SLSB的服务对象,存在比EJB线程管理更好的替代方案,包括:

Ø         实现不读/写实例变量的多线程服务对象。这种方法在servletStruts Action之类的对象中工作得非常完美。大多数无状态服务完全不需要复杂的线程管理。对于这类服务对象,只维护一个单独的实例(而不是维护一个实例池)在很多场合下都是有好处的。举例来说,如果我们因为效率的原因需要在一个无状态session bean中维护一个缓存(例如缓存一个开销庞大的计算得到的结果),EJB缓冲池将导致很多缓存同时存在。单独一个实例也许是更合理的办法。

Ø         使用Command模式:可以在收到每个请求时为每个服务对象创建一个新的实例,在对象内部消除并发的问题。WebWork/XWork很成功地采用了这个办法,在Spring中也同样可行——只要将bean定义为“prototype”类型即可。现代JVM很好地处理了对象的创建和销毁,服务对象通常可以很廉价地被实例化。

Ø         使用一个多线程服务对象,在其中使用普通Java语言的同步或并发类库以便保护任何读/写状态。如果需要控制的方法不多,同步实现起来很简单,工作起来也会很完美。当然同步不能阻止集群中另一台服务器上的同一段代码并发执行,但是EJB线程管理也同样做不到。

EJB实例池

与线程管理关系最为紧密的便是EJB的实例池。在各种EJB实例池中,最有价值的是无状态session bean缓冲池(这也是最简单的EJB实例池),因为无状态对象是缓冲池技术的理想用户。

在我看来,SLSB缓冲池的价值被高估了,尽管某些时候它确实有其价值。就像我曾经说过的,比起构思EJB规范时的Java 1.1 JVM,现代的JVM执行通常的垃圾收集要敏捷的多。如果时间回到1998年,我们的确需要实例池以避免垃圾收集器死掉,但现在真的还有这个必要吗?

某些应用服务器特有的EJB部署描述文件允许为每一个SLSB部署配置缓冲池大小,然而这种配置的价值是很可争议的。如果创建一个典型的SLSB只有相对较低的代价,那么配置一个比较小的缓冲池会产生不必要的竞争,而过大的线程池也不能改善吞吐量,因为吞吐量的决定因素是应用服务器所允许并发执行的线程的最大数量(线程池是一个应用服务器至关重要的服务,我将简短地加以讨论)。如果SLSB缓冲池的大小小于可以使用某个特定SLSB的最大线程数量,后来的调用将被阻塞,直到有一个EJB实例可以使用为止;如果SLSB缓冲池的大小大于线程的最大数量,那么吞吐量不会有任何提升。有人认为:不论任何场合,理想的缓冲池大小就是“希望访问一个特定SLSB的最大线程数量”,这种观点也同样是很可争议的。

设计EJB线程池的主要动机是避免垃圾收集和节省内存。比起Java 1.1 VM,现代JVM的垃圾收集效率要高得多;如今的内存也便宜得多。也就是说,比起当初,这两个动机如今都不是很重要了。

资源池

J2EE(或是其它三层应用)的缓冲池中,真正有价值的是资源池,而不是业务对象实例池。例如,将数据库连接放入缓冲池中,以避免在加载时耗尽连接,这是企业应用的基本策略。应用服务器也必须将执行线程放入缓冲池中,以防备拒绝服务攻击或是大负载时的失败。

然而,这样的资源池可以由J2EE应用服务器(而非EJB)提供,因此不必与EJB容器绑在一起。这样一来,任何一个运行于J2EE应用服务器中的对象(例如一个Servlet或者普通的Java类)都可以获得数据库连接池。在web应用中,web容器会在应用服务器的入口处强制使用线程池。并不需要用更多的线程池来保护业务对象,除非它在分布式应用中运行到了进程之外。

安全

另外一个EJB宣称提供的服务是安全管理。开发者可以在EJB部署描述文件中进行方法级的、基于角色的安全性配置(和Servlet API一样,EJB也支持编程性安全管理,可以在代码中检查一个通过了身份识别的用户是否具有某个特定的角色)。

单是捧着J2EE规范看看,这的确是很好,但实际用起来就不是那么好了。我见过不少J2EE应用有复杂的安全需求,它们大多发现J2EE安全基础架构根本不够用。这些应用没有一个能够依赖EJB的声明性、基于角色的安全:原因大抵是基于角色的安全不够用,或是需要一个动态安全凭证检查的级别、而EJB没有提供。这很大程度上是J2EE安全基础架构的问题,而不是EJB的,但仍然抵消了“安全性”为EJB挣来的分数。

声明性安全性确实有其价值,唯一的问题是:EJB的安全性服务必须绑在标准的J2EE安全基础架构上,而这些基础架构仍然无法满足复杂的需求。更好的办法是使用AOP来实现声明性自定义安全性:一般的做法是将安全凭证保存在一个ThreadLocal对象(而不是J2EE安全上下文)中,再用一个自定义的安全拦截器包裹在需要安全性管理的业务方法周围。

对于安全需求比较简单的web应用,通常在web层检查用户身份就足够了,例如借助Servlet API的声明性(基于web.xml)或者编程性基于角色的安全性。在web实现声明性安全管理的其他方法还有:向需要保护的内容周围添加Servlet filter;借助WebWork2Spring之类web框架中的拦截器;或者在web控制器中使用AOP拦截器。Spring等框架不仅提供了web MVC功能,还提供了AOP功能。

业务对象管理

最后,EJB容器也是一种工厂:它借助JNDI为服务对象(通常是SLSB)提供目录服务。EJB部署描述文件提供了一个从服务名称(JNDI 名称)到实现对象的映射,从而消除了业务接口(EJB的组件接口)与实现类(EJB实现类)之间的耦合。

这种解耦是一个好的编程实践。EJB的一大优点就是强制调用者使用业务接口,而不是直接使用实现类。随着EJB在实际工作中被广泛采用,它也在无意中普及了“针对接口编程、而非针对类编程”的原则。不过,EJB并不是达到“分离接口与实现”的唯一途径。

Java语言把接口作为头等公民来对待[3],从而提供了完美的解耦能力。不过,如果在Java代码中明确指定“使用接口的哪个实现类”,那么针对接口编程带来的好处也就被抵消殆尽了,因此我们需要一些基础设施提供帮助,从而可以在Java代码外部选择接口的实现类。EJB容器就是这样的基础设施——但不是唯一的,更不是最好的:有很多更加轻量的容器可供选择。Spring框架以一种非常轻量的方法帮助开发者实践“针对接口编程、而非针对类编程”的原则:它所提供的通用工厂可以根据配置(而不是Java代码)来按名字获得对象实例。如果想要切换某个接口的实现类,你只需要编辑一个简单的XML文件。另外,还有别的轻量级容器也可以达到类似的效果,而不需要EJB的复杂性,例如PicoContainerwww.picocontainer.org)。

由于SpringPicoContainer如此轻量级,因此它们EJB来更适合管理细粒度的业务对象——而EJB则只适合那些粗粒度的组件。对于细粒度的对象来说,实现、部署EJB和使用JNDI访问的开销太高了,即便这样可以使它们拥有“在不影响调用者的前提下切换实现”的能力也是得不偿失。换句话说, SpringPicoContainer这样的轻量级解决方案能够提供一步到位的工厂功能;而EJB只适合一部分应用场景,常常需要由组件facade之后的某种轻量级解决方案来补充。

总结:EJB的服务

上面所讨论的服务大多已经被加诸所有类型的EJB,然而除了SLSBMDB之外,别的EJB(尤其是entity bean)并不经常使用它们。例如,处于性能考虑,几乎任何场合都不会使用entity bean的远程接口。为entity bean提供事务管理也是毫无意义的:经验告诉我们,最好把entity bean设计成不包含业务逻辑的哑数据访问对象,而这样的对象不需要任何事务管理。

EJB帮助证实和普及了很多有价值的想法,但这并不意味着EJB的实现就是完美的。我们需要的解决方案原本可以不必这么复杂,这正是EJB的轻量级替代品出现的原由。有了过去七年的经验,以及从.NET中学到的东西,我们现在有机会既吸取EJB的优秀之处又避免其缺陷。我们可以拥有SLSB的一切长处,同时又避免所有不必要的复杂性。

EJB的现状

EJB仍然有其位置,但比很多J2EE开发人员所认为的位置要少得多。我可以确信,就在不长的几年之后,EJB将被归入“遗产系统”之列。然而,如果符合下列条件之一的话,现在使用EJB也是恰当的:

Ø         你在开发以中间件为核心的应用,其中web界面只扮演很少的角色,并且需要一个分布式(而不是简单的集群)体系架构。很多大型金融应用符合这个描述。然而,分布式体系结构常常被滥用,导致性能的损失和复杂性的提升,而没有带来任何真正的收获。

Ø         你想要开发一个基于RMI的分布式应用。EJB是达到这个目标的最简单的技术。

Ø         你确实需要有状态session bean,不能借助HTTP session对象或者厚客户端来满足这个需求:譬如说,你必须同时保存多种客户端的状态。在真实项目中,这种情况是相当罕见的。

不需要使用EJB来满足下列需求——它们常常被误认为是需要使用EJB的理由。

Ø         事务需求。事务协调器是由应用服务器(而非EJB容器)提供的。EJB CMT确实很好(特别是当与SLSB同时使用时),但我们还可以做得更好。

Ø         多线程。编写适用于多线程环境的无状态服务对象并不困难。如果需要缓冲池,Spring可以提供,并不需要EJB

Ø         资源池。这也是由J2EE(而非EJB)提供的。

即便不使用EJB,你也可以享受这些服务,而且不需要开发你自己的基础架构代码。

如果恰好有非常熟悉EJB的开发人员,你或许想要使用EJB。然而,由于EJB的复杂性,很多有EJB开发经验的开发人员实际上缺乏关于EJB的深度知识,而一知半解很可能是一件危险的事情。

我希望你从本文中得到的信息不止是“EJB是一种糟糕的技术”,更重要的是判断是否使用、何时使用EJB所需的基础知识。你应该能够为自己准备一个核对清单,用来检验EJB是否能为应用提供价值。如果所有条件都满足,就放心地使用EJB吧。

每次看到“四书五经”这个词,我的脑子里总是很煞风景地冒出宝二爷那句名言:“除《四书》外,杜撰的太多,偏只我是杜撰不成?”这“杜撰”二字,当指后世理学家们阐释孔孟之道的所谓“伪经”。在这样一个标题之下,今天我要推荐的书却多是旁人对J2EE的阐释。一个道貌岸然的标题,倒让我写成一个矛盾修辞法了。不过,对于一种已经有七年历史,并且即将改朝换代(下一版本的企业Java将改名为JavaEE)的技术而言,相信读者们需要的已经不是入门教材,而是关于“如何用它来开发真实应用”的经验之谈。本文将为读者推荐数本不同角度的J2EE最佳实践集锦,希望它们能够帮助熟悉Java编程、但对J2EE缺乏了解的读者描绘一幅这个庞大世界的导游图。

J2EE核心模式》(第二版)


Core J2EE Patterns (2nd Edition)Deepak Alur等著,刘天北等译,机械工业出版社,20055

译者为这本书所做的序言已足够直白:“如果说此前的各种教程都是在介绍J2EE开发中的‘内容’要素———也就是,教给我们‘做什么’———的话,本书关注的则是这里的‘形式’要素,是‘怎样做’才能开发出高效的、优雅的J2EE系统。读者从中学到的,将不仅仅是‘J2EE技术’,而是‘如何使用J2EE技术进行设计’。”不过,值得留意的是,这里所说的“J2EE技术”,应该更准确地描述为“Sun公司的J2EE技术”或者“正统J2EE技术”,即“基于EJBJ2EE技术”。由于某些原因(请容许我稍微卖个关子),这一类型的J2EE技术呈现出极大的复杂度,因此书中的解决方案(即“模式”)也往往呈现出令人敬畏的技巧。对于这本书,我的推荐意见是:读者应该牢记其中的每个解决方案以及对于这些解决方案的敬畏感——这种敬畏感将有助于你充分理解下一本书的价值所在。

Expert One-on-One J2EE Development without EJB


Rod Johnson
等著,JavaEye译,电子工业出版社,20058

揭开前面埋下的伏笔:“正统的”J2EE之所以那么复杂,很大程度上正是因为EJB的存在。而作为一个拥有十年Java经验和更长企业应用开发经验的开发者,Rod Johnson坚信这个世界上确实有很多不那么复杂的问题,而为这些问题找到同样不那么复杂的解决方案就是他(以及他的Spring框架)希望达到的目标。这本《J2EE Development without EJB》的妙处在于,它不仅指出了EJB的问题所在,更加阐述了一套完整自洽的、“Without EJB”的Java企业应用架构——全球第一大连锁超市沃尔玛的信息系统正是采用此架构搭建而成,这一事实足以证明该架构的合法性。之所以要将这本书放在《J2EE核心模式》之后推荐,是因为我担心读者在阅读《J2EE Development without EJB》之后再也没有兴趣去阅读前一本书,从而错失了充分了解EJB技术的一个机会。

更值得称道的是,Rod Johnson并不试图宣称自己的解决方案是Java企业应用的不二法门。贯穿全书,读者可以感觉到Johnson最希望传达给读者的是一种基于实践的“循证架构”方法。如果说这本书有其独到的功德,我想那不是因为它宣传了IoC或者AOP,而是因为它帮助一些读者破除了技术的门户之见,学会根据自己的需求和实践检验来选择架构。

《企业应用架构模式》


Patterns of Enterprise Application Architecture》,Martin Fowler著,王怀民等译,机械工业出版社,20047

读过前两本书之后,读者大概能够对J2EE的常见技术、问题和解决方案有所了解,随后粉墨登场的就该是Martin Fowler了。Fowler是一位善于总结他人经验的技术传教士,这本PoEAA便是他的典型作品之一:没有任何原创材料,却把很多“古而有之”的技术分析得丝丝入扣、阐述得鞭辟入里。其价值究竟有多大,只需看看诸如《J2EE核心模式》、《J2EE Development without EJB》、《.NET企业解决方案模式》一类好书有多么频繁地引用其中的内容,便可以知道大概。

这本PoEAA的缺点——和其他很多模式类书籍一样——是过于“形式化”:欠缺更具实际意义的范例,47个模式的列举与阐述多少显得有点干巴,而且对于“何时使用/不使用某个模式”这一问题的解答很难令人满意。不过,在本文涉及的几本书中,可能只有这一本是不需要额外推荐的,因为当你一次又一次地遇到别人在文章或交谈中不加解释地引用“Unit of Work”或者“Transaction Script”之类词汇之后,你很难不去读这本PoEAA

Enterprise Integration Patterns


Gregor Hohpe等著,Addison-Wesley 200310月(暂无中译本)

这本书中有一句话深得我心:“如果有人跟你说企业应用集成是件很轻松的事,这人要么是聪明得出奇,要么是傻得出奇,要么就是出于商业原因希望让你相信他即将兜售的某种东西。”对于习惯了面向对象的大多数Java程序员而言,充斥异步/跨进程调用的企业应用集成(EAI)不啻是一场噩梦;而在银行/保险等信息化较早的机构中,EAI的需求又偏偏如同家常便饭。两者之间的张力,使得这本书——在某些特定的时候,对于某些特定的人——有如天籁一般。在某种意义上,这本书对于破除大词迷信也有一定的帮助:它将“面向服务架构”(SOA)作为企业应用集成的六种应用类型之一加以阐述,并总结了各种类型的适用场景和优缺点。Martin Fowler为它做的序中称其为“PoEAA的姊妹书”——从填补了PoEAA所没有覆盖的一大类企业应用场景这一角度来说,这一称号是名副其实的,而作者的技术与文笔也对得起这一赞誉。

Java Modeling in Color with UML


Peter Coad著,Prentice Hall 19996月(暂无中译本)

推荐最后这本书的目的是明确的:一位称职的J2EE开发者应该具备一定的领域建模能力。但从知名度上来说,被推荐的对象似乎应该是《分析模式》或者《Domain Driven Design》,而不是这本几乎从来没在国内引起过关注的“小书”。遗憾的是,Martin Fowler那本书缺乏对实践经验的归纳总结,而Eric Evans那本对于“怎么把业务概念变成领域模型”这件最后的、却绝非最不重要的事情语焉不详。不过好在Peter Coad是出了名的鬼才,惟其如此才能保证区区221页内容确实言之有物。

从计算机科学的角度来分析,越是形式化、可递归应用的方法就越具有可操作性。Eric EvansDDD在“理解需求”方面的阐述很具可操作性,而Peter Coad提出的几类基本元模型对于实际进行建模工作有着非比寻常的指导价值——当大多数人在分析业务领域模型时,Peter Coad在分析业务领域的元模型,其“鬼才”由此可见一斑。至于“带颜色的UML”,无非是对元模型的一种直观描述而已。对于面向对象(而非面向用例)的企业应用业务建模,这本“小书”便是首屈一指的最佳实践指南。

* * *

* * *

读者可以看到,在我推荐的五本书中,既没有介绍时下流行的HibernateSpring等框架的专著,也没有讲述AOPAJAX之类新兴技术的著作。J2EE是一个实用至上的领域,尤其是在它已经完全成熟的今天,或许更有价值的是“如何使用”的指导。在我的推荐之中不乏已问世三、五年的“旧书”,由此或许可以证明:越陈越香的大概不只是美酒,还包括技术的积淀。

2005年11月10日
  今天,越来越多的开发者想要编写企业级的分布式的事务处理应用程序,而这些应用程序必须可以发挥速度、安全性和服务器端技术的可靠性。如果你已经在这一领域从事工作,你应该了解在现在这个高速发展、要求苛刻的电子商务和信息技术的世界中,企业级的应用程序必须具有以下特点:花费更少的金钱、具有更快的速度、占用更少的资源。

  为了减少费用,快速设计和开发企业级的应用程序,Java 2 Platform, Enterprise Edition (J2EE)技术提供了一个基于组件的方法来设计、开发、装配和部署企业级应用程序。J2EE平台提供了一个多层结构的分布式的应用程序模型,该模型具有重用组件的能力、基于扩展标记语言(XML)的数据交换、统一的安全模式和灵活的事务控制。你不仅可以比以前更快地发表对市场的新的解决方案,而且你的独立于平台的基于组件的J2EE解决方案不再受任何提供商的产品和应用程序编程界面(APIs)的限制。提供商和买主都可以自己选择最合适于它们的商业应用和所需技术的产品和组件。

  1、分布式的多层应用程序

  J2EE平台使用了一个多层的分布式的应用程序模型。应用程序的逻辑根据其实现的不同功能被封装到组件中,组成J2EE应用程序的大量应用程序组件根据在其所属的多层的J2EE的环境中所处的层被安装到不同的机器中。图1-1表示了两个多层的J2EE应用程序根据下面的描述被分为不同的层。在图1-1中涉及的J2EE应用程序的各个部分将在J2EE组件中给出详细描述。

  1、运行在客户端机器的客户层组件。
  2、运行在J2EE服务器中的Web层组件。
  3、运行在J2EE服务器中的商业层组件。
  4、运行在EIS服务器中的企业信息系统(EIS)层软件。

  尽管从图1-1中可以看到J2EE应用程序既可以是三层结构,也可以是四层结构,但是我们通常将J2EE应用程序的多层结构考虑为三层结构。这是因为它们分布在三个不同的位置:客户端机器、J2EE服务器机器和在后端的传统的机器。三层结构的应用程序可以理解为在标准的两层结构的客户端/服务器模式的客户端应用程序和后端存储资源中间增加了一个多线程的应用程序服务器。



图1-1:多层结构的应用程序
  2、J2EE组件

  J2EE应用程序由组件组成。一个J2EE组件就是一个自带功能的软件单元,它随同它相关的类和文件被装配到J2EE应用程序中,并实现与其它组件的通信。J2EE规范是这样定义J2EE组件的:
  客户端应用程序和applet是运行在客户端的组件。
  Java Servlet和JavaServer Pages (JSP)是运行在服务器端的Web组件。
  Enterprise JavaBean(EJB)组件(enterprise bean)是运行在服务器端的商业软件。
  J2EE组件由Java编程语言写成,并和用该语言写成的其它程序一样进行编译。J2EE组件和"标准的"Java类的不同点在于:它被装配在一个J2EE应用程序中,具有固定的格式并遵守J2EE规范,它被部署在产品中,由J2EE服务器对其进行管理。

  3、J2EE客户端

  一个J2EE客户端既可以是一个Web客户端,也可以是一个应用程序客户端。

  3.1 Web客户端
  一个Web客户端由两部分组成:由运行在Web层的Web组件生成的包含各种标记语言(HTML、XML等等) 的动态Web页面和接受从服务器传送来的页面并将它显示出来的Web页面。
  一个Web客户端有时被称之为瘦客户端。瘦客户端一般不做象数据库查询、执行复杂的商业规则及连接传统应用程序这样的操作。当你使用一个瘦客户端时,象这样的重量级的操作被交给了在J2EE服务器执行的enterprise bean。这样就可以充分发挥J2EE服务器端技术在安全性、速度、耐用性和可靠性方面的优势。

  3.2 Applets
  从Web层接收的一个Web页面可以包含内嵌的applet。一个applet是一个用Java编程语言编写的小的客户端应用程序,它在安装在Web浏览器中的Java虚拟机中运行。然而,为了在Web浏览器中成功地运行applet,客户端系统很可能需要Java插作和安全策略文件。
  Web组件是用来建立一个Web客户端程序的首选的API,因为这样在客户端系统中就不需要插件和安全策略文件。同样的,使用Web组件可以有效地改善应用程序设计,因为它们提供了一个将应用程序设计和Web页面设计有效分离的途径。Web页面的设计者可以不必关心Java编程语言的语法就能很好地完成自己的工作。
3.3 应用程序客户端
  一个J2EE应用程序客户端运行在客户端机器上,它使得用户可以处理需要比标记语言所能提供的更丰富的用户界面的任务。具有代表性的是用Swing或抽象窗口工具包(AWT)API建立的图形用户界面(GUI),但是一个命令行界面也是当然可能的。
  应用程序客户端直接访问运行在商业层的enterprise bean。然而,如果应用程序需要授权, 一个J2EE应用程序客户端可以打开一个HTTP连接来与一个运行在Web层的servlet建立通信。

  4、JavaBeans组件体系结构

  服务器层和客户层也可以包含以JavaBean组件体系结构(JavaBeans组件)为基础的组件来管理在一个应用程序客户端或applet与运行在J2EE服务器上的组件之间的数据流动以及服务器端组件与数据库之间的数据流动。在J2EE规范中JavaBeans组件不被认为是J2EE组件。

  JavaBeans组件具有实例变量以及用来访问实例变量中的数据的get方法和set方法。作这种用途的JavaBeans组件在设计和执行时相当简单,但是它必须遵守JavaBeans组件体系结构的命令和设计惯例。

  5、J2EE服务器通信

  图1-2显示了客户层组成的多种方式。客户端可以直接和运行在J2EE服务器中的商业层进行通信。如果是一个运行在浏览器中的客户端,也可以通过运行在Web层中的JSP页面和Servlet进行这种通信。
你的J2EE应用程序是采用瘦客户端还是胖客户端。要作出这样的决定,你应该明白将胖客户端是将功能留在客户端,使它与用户更接近,而瘦客户端是将功能的实现尽可能地交给服务器。由服务器处理更多的功能,就更容易分发、部署和管理应用程序;而将更多的功能留在客户端对于有经验的用户也许是个明智的选择。



图1-2 服务器通信
  5.1 Web组件
  J2EE的Web组件既可以是servlet也可以是JSP页面。Servlets是一个Java编程语言类,它可以动态地处理请求并作出响应。JSP页面是一个基于文本的文档,它以servlet的方式执行,但是它可以更方便建立静态内容。
  在装配应用程序时,静态的HTML页面和applet被绑定到Web组件中,但是它们并不被J2EE规范视为Web组件。服务器端的功能类也可以被绑定到Web组件中,与HTML页面一样,它们也不被J2EE规范视为Web组件。
正如图1-3中所示,和客户层一样,Web层也可以包含一个JavaBeans组件以管理用户的输入并将输入发送到运行在商业层的enterprise bean进行处理。



图1-3 Web层和J2EE应用程序
  5.2 商业组件
  商业代码,表示了例如银行、零售和财政这样的特定的商业领域的相适应的逻辑。它由运行在商业层的enterprise bean处理。图1-4显示了一个enterprise bean如何从客户端接受数据,对它进行处理(如果需要),并将其发送到企业信息系统层以作存储。一个enterprise bean也可以从存储器获取数据,对它进行处理(如果需要),并将其发送到客户端应用程序。



图1-4 商业层和EIS层

有三种类型的enterprise beans:session beans、entity beans和message-driven beans。一个session bean描述了与客户端的一个短暂的会话。当客户端的执行完成后,session bean和它的数据都将消失。与些相对应的是一个entity bean描述了存储在数据库的表中的一行的持久稳固的数据。如果客户端终止或者服务结束,底层的服务会负责entity bean数据的存储。
  一个message-driven bean结合了一个session bean和一个Java信息服务(JMS)信息监听者的功能,它允许一个商业组件异步地接受JMS消息。这份指南只介绍entity bean和session bean。有关message-driven bean的介绍,请参看Java消息服务指南:
http://java.sun.com/products/jms/tutorial/index.html

  6、企业信息系统层

  企业信息系统层处理企业信息系统软件并包含诸如企业资源计划(ERP)、主机事务处理、数据库系统和其它传统系统这样的底层系统。J2EE应用程序组件可能需要访问企业信息系统,例如是获得一个数据库连接。

  6.1 J2EE容器
  通常,瘦客户端的多层应用程序是很难编写的,这是因为这得包括许多行复杂的代码以处理事务、状态管理、多线程、资源池和其它复杂的底层详细资料。基于组件并与平台无关的J2EE体系结构使得J2EE应用程序易于编写,这是因为商业逻辑被封装到可重用的组件中,此外,J2EE服务器以容器的形式为每一个组件类型提供底层服务。因为我们不需要自己开发这些服务,这使我们可以全力以赴地着手处理商业问题。

  6.2 容器服务
  容器是一个组件和支持组件的底层平台特定功能之间的接口,在一个Web组件、enterprise bean或者是一个应用程序客户端组件可以被执行前,它们必须被装配到一个J2EE应用程序中,并且部署到它们的容器。
装配的过程包括为J2EE应用程序中的每一个组件以及J2EE应用程序本身指定容器的设置。容器设置定制了由J2EE服务器提供的底层支持,这将包括诸如安全性、事务管理、Java命名目录接口(JNDI)搜寻以及远程序连接。下面是其中的主要部分:

  1、J2EE的安全性模式可以让你对一个Web组件或enterprise bean进行配置以使得只有授权用户访问系统资源。
  2、J2EE的事务模式可以让你指定方法之间的关系以组成一个单个的事务,这样在一个事务中的所有方法将被视为一个单一的整体。
  3、JNDI搜寻服务为企业中的多种命名目录服务提供一个统一的接口,这使得应用程序组件可以访问命名目录服务。
  4、J2EE远程连接模式管理客户端和enterprise bean之间的底层通信。在一个enterprise bean被建立后,客户端在调用其中的方法时就象这个enterprise bean就运行在同一个虚拟机上一样。

  实际上,J2EE体系结构提供了可配置的服务意味着在相同的J2EE应用程序中的应用程序组件根据其被部署在什么在地方在实际运行时会有所不同。例如,一个enterprise bean可能在一个产品环境中拥有包含访问数据库数据的某种级别的安全性设置,而在另一个产品环境中是另一个访问数据库的级别。
  容器还管理诸如一个enterprise bean和servlet的生存周期、数据库连接资源池以及访问在J2EE APIs中介绍的J2EE平台API这样不能配置的服务。尽管数据持久化是一个不能配置的服务,但是J2EE体系统结构允许你在你想要获得比默认的容器管理持久化所能提供更多的控制时,通过在你的enterprise bean执行中包含适当的代码以重载容器管理持久化。例如,你可以使用bean管理持久化以实现你自己的finder(查找)方法或者是建立一个定制的数据库缓冲区。

  6.3容器类型
  部署时会将J2EE应用程序组件安装到J2EE容器中,就象图1-5中所示那样。

  1、J2EE服务器:是J2EE产品的运行部分。一个J2EE服务器提供EJB容器和Web容器。Enterprise JavaBeans(EJB)容器:管理J2EE应用程序的enterprise bean的执行。Enterprise bean和它的容器运行在J2EE服务器中。
  2、Web容器:管理J2EE应用程序的JSP页面和servlet组件的执行。Web组件和它的容器也运行在J2EE服务器中。
  3、客户端应用程序容器:管理应用程序客户端组件的运行。应用程序客户端和它的容器运行在客户端中。
  4、Applet容器:管理applet的执行。由运行在客户端的一个Web浏览器和Java插件一同组成。





图1-5 J2EE服务器和容器

7、封装

  J2EE组件被分别打包并绑定到一个J2EE应用程序中以供部署。每一个组件、它的诸如GIF、HTML文件和服务器端功能类这样的相关文件以及一个部署说明组成了一个模块并被添加到J2EE应用程序中。一个J2EE应用由一个或几个enterprise bean组件模块、Web组件模块或应用程序客户端组件模块组成。根据不同的设计需求,最终的企业解决方案可以是一个J2EE应用程序,也可以由两个或更多的J2EE应用程序组成。
  一个J2EE应用程序以及它的每一个模块有它自己的部署说明。一个部署说明就是一个具有.xml扩展名的XML文件,它描述了一个组件的部署设置。例如,一个enterprise bean模块的部署说明,描述了一个enterprise bean的事务属性和安全性授权。因为部署说明信息是公开的,因此它可以被改变而不必修改bean的源代码。在运行时,J2EE服务器将读取这个部署说明并遵照执行。
  一个J2EE应用以及它的所有模块被提交到一个Enterprise ARchive (EAR)文件中。 一个EAR文件就是一个具有.ear扩展名的标准的Java Archive (JAR)文件。在J2EE SDK应用程序部署工具的GUI版本中,你首先建立一个EAR文件,并在其中添加JAR文件和Web Archive(WAR)文件。然而,如果你使用的是命令行的打包工具,你必须首先建立JAR和WAR文件,然而才是建立EAR文件。有关J2EE SDK工具的详细介绍请参看工具。

  1、每一个EJB JAR文件包含一个部署说明、enterprise bean文件以及相关的文件。
  2、每一个应用程序客户端JAR文件包含一个部署说明、应用程序客户端的类文件以及相关的文件。
  3、每一个WAR文件包含一个部署说明、Web组件文件以及相关的资源。

  使用模块和EAR文件使得运用同一组件以装配许多不同的J2EE应用程序成为可能。不需要额外的编程工作,你唯一要做的是在J2EE EAR文件中添加各种J2EE模块。

  8、开发角色

  可重用的模块使得将应用程序的开发过程和部署过程分成不同的角色成为可能。这样,不同的人或公司可以在这一过程的各个部分承担不同的任务。
  最先的两个角色承担购买和安装J2EE产品和工具的任务。在购买和安装好软件之后,应用程序组件提供商就开始开发J2EE组件,应用程序装配者负责装配,而应用程序部署者负责部署。在一个大的组织中,每个这样的角色可能对应于不同的个人和小组。作这样的分工是因为前一个角色将会输出一个可移植的文件,而这将是后一个角色的输入。例如,在应用程序组件提供商阶段,一个enterprise bean软件开发者提交EJB JAR文件。而在应用程序装配者阶段,另一些开发者将这些EJB JAR文件组合到一个J2EE应用程序中并将其保存为一个EAR文件。在应用程序部署者阶段,消费者站点的系统管理员使用EAR文件将J2EE应用程序安装到J2EE服务器中。
  不同的角色不一定总得由不同的人来执行。例如,你是在一个小公司工作或者你是从事一个样本程序的原型开发,你也可能需要执行每一个阶段的任务。

  9、J2EE产品提供者

  J2EE产品提供者是设计并提供实现J2EE规范所定义的J2EE平台、API和其它功能的公司。这一般是指操作系统、数据库系统、应用程序服务器或Web服务器的卖主,它们依照J2EE的规范实现J2EE平台。

  9.1工具提供者
  工具提供者是指建立组件提供者、装配者和部署者所使用的开发、装配和打包的工具的公司或个人。有关J2EE SDK 1.3可用工具的详细信息参见工具。

  9.2 应用程序组件提供者
  应用程序组件提供者是指建立J2EE应用程序所使用的Web组件、enterprise bean、applet或应用程序客户端的公司或个人。

  9.3 Enterprise Bean开发者
  一个enterprise bean开发者执行下面的任务并递交一个包含enterprise bean的EJB JAR文件:

  1、编写并编译源代码
  2、详细说明部署描述
  3、将.class文件和部署描述绑定到一个EJB JAR文件中

  9.4 Web组件开发者
  一个Web组件开发者执行下面的任务并提交一个包含Web组的WAR文件:

  1、编写并编译servlet源代码
  2、编写JSP和HTML文件
  3、详细说明Web组件的部署描述
  4、将.class、.jsp、.html和部署描述绑定到WAR文件中

  9.5 J2EE应用程序客户端开发者
  一个应用程序客户端开发者执行下面的任务并提交一个包含J2EE应用程序客户端的JAR文件:

  1、编写并编译源代码
  2、详细说明客户端的部署描述
  3、将.class文件和部署描述绑定到JAR文件中

  9.6 应用程序装配者
  应用程序装配者是从组件提供者接受应用程序组件JAR文件并将其装配到一个J2EE应用程序EAR文件中的公司或个人。装配者或部署者可以直接编辑部署说明或者使用可以根据交互式的正确地添加XML标志的工具。一个软件开发者执行下列任务并递交一个包含J2EE应用程序的EAR文件:

  1、将上一阶段建立的EJB JAR文件和WAR文件装配到一个J2EE应用程序(EAR)文件中。
  2、详细说明有关J2EE应用程序的部署说明。
  3、检验EAR文件中的组件是否遵守J2EE规范。
9.7 应用程序部署者和系统管理员
  应用程序部署者和系统管理员可以是公司或个人,他们配置和部署J2EE应用程序,管理J2EE应用程序在其中运行的计算机和网络这些低层结构,并对运行环境进行监控。他们的任务可能包括这样的一些事:设置事务控制、安全属性并指定数据库连接。

  在配置时,部署者按照由应用程序组件提供者提供的指示以解决外部的支持、指定安全设置并定义事务属性。在安装时,部署者将应用程序组件装入服务器中并生成特定容器的类和接口。

  一个部署者/系统管理员执行下列任务以安装和配置一个J2EE应用程序:

  1、将上一阶段生成的J2EE应用程序(EAR)文件添加到J2EE服务器中
  2、根据运行环境通过修改J2EE应用程序的部署说明对J2EE应用程序进行配置。
  3、检验EAR文件的内容是否遵守J2EE规范
  4、部署(安装)J2EE应用程序EAR文件到J2EE服务器中

  10、相关执行软件

  J2EE SDK是非经营的J2EE平台的操作定义,它由Sun公司提供可以免费用于示范、试验和教育用途。它包含了J2EE应用程序服务器、Web服务器、关系型数据库、J2EE API和一整套开发和部署工具。你可以从这里下载J2EE SDK:
  http://java.sun.com/j2ee/download.html#sdk

  J2EE SDK的目的是为了允许产品提供者用来测试它们的执行是否需要一系列特定的条件,并可以运行J2EE兼容性测试以确定他们的J2EE产品是否完全遵守J2EE规范。J2EE SDK还可以由应用程序组件开发者用来运行他们的J2EE应用程序以检验它们是否完全适合所有的J2EE产品和工具。

  10.1数据库访问
  关系型数据库为应用程序数据提供了持久稳固的存储。一个J2EE执行并不需要支持特定类型的数据库,这意味着不同的J2EE产品所支持的数据库可以改变。参看下载的J2EE SDK所包含的版本说明可以了解目前所支持的数据库。

  10.2 J2EE API
  J2SE SDK对于J2EE SDK的运行是必需的,它可以为编写J2EE组件提供核心的API、核心的开发工具和Java虚拟机。J2EE SDK为J2EE应用程序提供下面的API以供使用。

  10.3 Enterprise JavaBeans技术2.0
  一个enterprise bean是一个用来执行商业逻辑的模块的具有字段和方法的代码实体。你可以将一个enterprise bean想象成一块积木,它可以在J2EE服务器上单独使用也可以与其它enterprise bean协同工作以执行商业逻辑。
  有三种类型的enterprise bean:session bean、entity bean和message-driven bean。Enterprise bean经常与数据库有一个交互的关系。使用entity bean的一个好处是你不需要编写任何SQL代码或使用JDBC API以直接执行数据库访问操作;EJB容器会为你处理这些事。然而,如果因为某种原因,你重载了默认的容器管理持续化,你需要使用JDBC API。同样的,如果你选择一个session bean来访问database,你也必须使用JDBC API。

  10.4 JDBC API 2.0
  JDBC API让你可以从Java编程语言的方法中调用SQL命令。在一个enterprise bean中,当你重载了默认的容器管理持续化或是在一个session bean中访问数据库时,你必须使用JDBC API。当使用容器管理持续化时,数据库访问的操作是由容器来处理的,你的enterprise bean执行不包含任何JDBC代码或SQL命令。你也可以使用JDBC API从一个servlet或JSP页面中直接访问数据库,而不通过一个enterprise bean来完成。
JDBC API有两个部分:一个由应用程序组件用来访问数据库的应用程序级的接口,一个用来将JDBC驱动整合到J2EE平台中的服务提供商接口。

  10.5 Java Servlet技术2.3
  Java Servlet技术允许你定义一个特定的HTTP的servlet类。一个servlet类为服务器扩展了这样一个性能:可以通过请求-响应这样的程序模式访问主机应用程序。尽管servlet可以响应任何形式的请求,但是它们通常用于Web服务器上的应用程序。

  10.6 JavaServer Pages技术1.2
  JavaServer Pages技术使得你可以将servlet代码嵌入到基于文本的文档中。一个JSP页面就是一个基于文本的文档,它包含两个类型的文本:静态模板的数据,它可以表示为任何基于文本的格式,例如HTML、WML和XML;JSP单元,它决定页面如何建立动态的内容。

  10.7 Java消息服务(JMS)1.0
  JMS是一个消息标准,它允许J2EE应用程序建立、发送、接受和阅读消息。它使得建立连接简单的、可靠的和异步的公布式通信成为可能。有关JMS的更多的介绍,请参看Java消息服务指南:
  http://java.sun.com/products/jms/tutorial/index.html

  10.8 Java命名目录接口(JNDI)1.2
  JNDI提供命名的目录功能。它为应用程序提供标准的目录操作的方法,例如获得对象的关联属性、根据它们的属性搜寻对象等。使用JNDI,一个J2EE应用程序可以存储和重新得到任何类型的命名Java对象。
因为JNDI不依赖于任何特定的执行,应用程序可以使用JNDI访问各种命名目录服务,包括现有的各种诸如LDAP、NDS、DNS和NIS这样的命名目录服务。这使得J2EE应用程序可以和传统的应用程序和系统共存。有关JNDI的更多的信息,请参看JNDI指南:
  http://java.sun.com/products/jndi/tutorial/index.html

  10.9 Java事务API 1.0
  Java事务API(JTA)提供了划分事务的标准接口。J2EE体系结构提供了一个默认的自动提交以处理事务提交和回滚。一个自动提交意味着在每一个数据库读写操作之后任何其它应用程序显示数据时都会看到更新了的数据。然而,如果你的应用程序执行两部分相互依赖的数据库访问操作,你可能会想要用JTA API去确定整个事务,这个事务将包含两个操作的开始、回滚和提交。

  10.10JavaMail API 1.2
  J2EE应用程序可以使用JavaMail API来发送e-mail告示。JavaMail API包含两部分: 一个由应用程序组件用来发送mail的应用程序级的接口和一个服务提供接口。J2EE平台包含作为服务提供的JavaMail,使得应用程序组件可以发送Internet mail。

  10.11 JavaBeans激活架构1.0
  之所以要包含JavaBeans激活架构(JAF)是因为JavaMail要使用到它。它提供标准的服务以确定任意数据段的类型、如何对它访问、找出在其上可应用的操作并建立适当的JavaBeans组件以执行那些操作。
10.12 Java XML处理API1.1
  XML是一种描述基于文本的数据的语言,使用XML使得数据可以被任何程序和工具读取和处理。程序和工具可以生成其它程序和工具可以读取和处理的XML文档。Java XML处理API(JAXP)支持使用DOM、SAX和XSLT对XML文档进行处理。JAXP使得应用程序可以不依赖于特殊的XML处理执行来解析和转换XML文档。
  例如,一个J2EE应用程序可以使用XML来生成报表,而不同的公司都可以获得这个报表并使用各自最适宜的方法来处理它。一个公司可能会通过程序将XML数据导入到HTML中以使得其可以在网站中公布,另一个公司可能会通过工具导出XML数据以制定销售预算,而另一个公司可能会将XML数据导入它的J2EE应用程序中对其进行处理。

  10.13 J2EE连接器体系结构1.0
  J2EE工具提供商和系统综合者使用J2EE连接器体系结构建立可以加入到任何J2EE产品的支持访问企业信息系统的资源适配器。一个资源适配器就是一个使得J2EE应用程序组件可以访问底层的资源管理器并与其实现交互的软件组件。因为一个资源适配器是与它的特定的资源管理器相对应的,典型的情况是不同的数据库或企业信息系统会各自有其不同的资源适配器。

  10.14 Java认证和授权服务1.0
  Java认证和授权服务(JAAS)为J2EE应用程序提供了一个方法以为一个特定的用户或一组用户进行认证和授权。
  JAAS是标准的可插入认证模块(PAM)结构的Java版本,它对Java 2平台的安全认证框架进行了扩展以支持基于用户的安全认证。

  11、简单的系统集成

  J2EE平台具有平台无关性,全部的系统集成解决方案建立了一个开放的市场,在这个市场中,每个提供商都可以向所有的用户出售他们的产品。这样的一个市场鼓励提供商进行竞争,不是试图将通过技术困缚用户,而是通过提供比其它提供商更好的产品和服务,例如是更好的性能、更好的工具或更好的用户支持。
J2EE API使得系统和应用程序集成具有下面的这些特点:

  1、enterprise beans所支持的统一的应用程序多层结构
  2、JSP页面和servlet所支持的简单的请求和响应机制
  3、JAAS所支持的可靠的安全模型
  4、JAXP所支持的基于XML的数据交换集成
  5、J2EE连接器体系结构所支持的简单的协同工作能力
  6、JDBC API所支持的方便的数据库连通性
  7、message-driven beans、JMS、JTA和JNDI所支持的其它特性

  要学习更多的有关使用J2EE平台以建立商业综合系统的知识,你可以参阅J2EE技术实践:http://java.sun.com/j2ee/inpractice/aboutthebook.html

  11.1工具
  J2EE实现标准提供了一个应用程序部署工具和一系列命令以装配、校验和部署J2EE应用程序和管理你的部署和产品环境。

  11.2应用程序部署工具
  J2EE实现标准提供了一个应用程序部署工具(deploytool)以装配、校验和部署J2EE应用程序。这个工具有两个版本:命令行和GUI。

  GUI工具包括下列向导

  1、打包、配置和部署J2EE应用程序
  2、打包和配置enterprise bean
  3、打包和配置Web组件
  4、打包和配置应用程序客户端
  5、打包和配置资源适配置器
  6、此外,还可以在tabbed inspector pane中对每个组件和模块类型的配置信息进行设置。

  11.3命令
  表1-1列出了J2EE实现标准中所包含的命令,你可以使用这些命令在命令行执行操作。



表1-1 J2EE命令

j2ee

启动和终止J2EE服务

cloudscape

启动和终止默认的数据库

j2eeadmin

增加JDBC驱动、JMS目的文件以及不同资源的连接factory

keytool

建立公钥和私钥,并生成X509自签署证书。

realmtool

导入证书文件。为一个J2EE应用程序的认证和授权列表中增加或删除J2EE用户

packager

将J2EE应用程序组件打包到EAR、EJB JAR、应用程序客户端JAR或WAR文件中

verifier

校验EAR、EJB JAR、应用程序客户端JAR和WAR文件是否符合并遵守J2EE规范

runclient

运行一个J2EE应用程序客户端

cleanup

从J2EE服务器中删除所有已部署的应用程序