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月29日

      为了庆祝我和LG顺利通过答辩,昨天我们去庆祝了一下,美美地吃了一顿水煮鱼。本来答辩当天我们就想去来着,不过那天感觉太累了,可能是脑细胞牺牲太多的缘故吧~~~

      去到我们喜欢的那家水煮鱼店时,才发现他们重新装修了,想来我们也有好久好久没有来过了,不过店里的经理还是马上认出了我们,厉害。店里正在促销,消费过100就可以赠送一大盘香辣虾,而且下次还可以免费吃鱼,嘎嘎,真是有口福呀。

      水煮鱼发展了一代又一代,不过我和LG都还是喜欢吃最初的那种浮满红辣椒的第一代水煮鱼。这里的味道做得非常地道,吃起来又香又辣,过瘾得很。赠送的那盘香辣虾味道也非常棒,好大的一盘,哪里像是赠送的,真实惠。

      吃得实在是太爽了,我们消灭了所有的美食,临走还给了一张鱼票和一张积分卡,现在想象还满口余香……

2005年11月24日
  用手机上网已经有好长一段时间了,每次在和朋友交流经验时,总会发现很多朋友对CMWAP和CMNET的区别还是不甚了解。对于一些新软件,经常有人会问“CMWAP可以用吗?”,所以笔者撰写了此文,希望本文能为大家解开这个疑惑。


  问题1.为什么会有两个接入点?

  在网上查阅大量资料后并经过反复的尝试与探索后,笔者对CMWAP和CMNET两种上网方式作出了一下总结:在国际上,通常只有一种GPRS接入方式,为什么在中国会有CMWAP和CMNET两兄弟呢?(彩信之所以单独配置接入点是因为彩信服务需要连接专用的服务器,在这里不作探讨。)其实,CMWAP 和 CMNET 只是中国移动人为划分的两个GPRS接入方式。前者是为手机WAP上网而设立的,后者则主要是为PC、笔记本电脑、PDA等利用GPRS上网服务。它们在实现方式上并没有任何差别,但因为定位不同,所以和CMNET相比,CMWAP便有了部分限制,资费上也存在差别。

  问题2.什么是WAP?

  WAP只是一种GPRS应用模式,它与GRPS的接入方式是无关的。WAP应用采用的实现方式是“终端+WAP网关+WAP服务器”的模式,不同于一般Internet的“终端+服务器”的工作模式。主要的目的是通过WAP网关完成WAP-WEB的协议转换以达到节省网络流量和兼容现有WEB应用的目的。

  WAP网关从技术的角度讲,只是一个提供代理服务的主机,它不一定由网络运营商提供。但据我所知,中国移动GPRS网络目前只有唯一的一个WAP网关:10.0.0.172,有中国移动提供,用于WAP浏览(HTTP)服务。有一点需要注意,WAP网关和一般意义上的局域网网关是有差别的,标准的WAP网关仅仅实现了HTTP代理的功能,并未完成路由、NAT等局域网网关的功能。这就决定了它在应用上所受到的限制。

  问题3.中国移动对CMWAP的限制?

  为了从应用中区别两者的定位,中国移动对CMWAP作了一定的限制,主要表现在CMWAP接入时只能访问GPRS网络内的IP(10.*.*.*),而无法通过路由访问Internet。(少数地区的移动网络可能不存在这一限制。)我们用CMWAP浏览Internet上的网页就是通过WAP网关协议或它提供的HTTP代理服务实现的。

  说到这里,就让笔者自然而然的联想到公司的网络,相信不少工作的朋友都有类似的体会。公司的网络在网关上不提供路由和NAT,仅仅提供一个可以访问外网的HTTP代理。这样,我们就无法直接使用QQ、MSN等非HTTP协议的应用软件了(好在它们还提供的有HTTP代理的连接方式),CMWAP也正是。

  问题4.CMWAP和CMNET的适用范围

  两种上网连接方式的适用范围才是大家最关心的问题。CMNET拥有完全的Internet访问权,这里就不多说了,主要让我们来看看CMWAP。因为有了上面提到的限制,CMWAP的适用范围就要看WAP网关所提供的支持了。目前,中国移动的WAP网关对外只提供HTTP代理协议(80和8080端口)和WAP网关协议(9201端口)。

  因此,只有满足以下两个条件的应用才能在中国移动的CMWAP接入方式下正常工作:

  1. 应用程序的网络请求基于HTTP协议。

  2. 应用程序支持HTTP代理协议或WAP网关协议。

  问题5.如何辨别一个应用程序的网络请求是否基于HTTP协议?

  这个问题还真不好回答,要完全做到这一点需要通过拦截应用程序的通信数据包进行分析。这里提供几个简单的方法给广大朋友:从表现上看,如果它的网络请求是网址(URL)的形式,那么通常是基于HTTP协议的,如Web浏览器;如果它连接的服务器端口是80,那么它可能是基于HTTP协议的。

  问题6.如何区别一个应用程序支持HTTP代理协议还是WAP网关协议呢?

  首先看它的设置中有没有代理服务器的选项,如果有则表示它支持HTTP代理协议。如果没有,则需要按照以下步骤测试:

  在GPRS接入点设置的高级设置里去掉代理服务器的设置项:Server Address 和 Server Port,如果应用程序可以正常工作,那么它是基于WAP网关协议,如Java程序、S60内置的浏览器。如果在此状态下不能正常工作,而恢复GPRS接入点高级设置中的代理服务器设置后能够正常工作,则应用程序支持HTTP代理协议(代理设置从系统中读取)。如果仍不能正常工作,那么这个应用程序一般来说是不支持HTTP代理协议或WAP网关协议的。

  这里需要特别说明的是JavaQQ,它有Socket和HTTP两种版本。现在网上流传的可用于CMWAP的JavaQQ就是基于HTTP协议的。就拿那个JavaQQ 2004来说,启动画面中就明确的写着“KJava QQ HTTP”。而SIS版的QQ和AgileMessenger(S60的MSN客户端)因为是采用的普通的Socket连接方式,因此无法用于CMWAP。

  最后笔者总结了一下,CMWAP下可以使用的常见软件的工作方式:

  (1) 手机内置的浏览器:WAP网关协议

  (2) Opera 浏览器:HTTP代理协议(有代理设置)

  (3) Java 程序:WAP网关协议

  (4) AvantGo:HTTP代理协议(有代理设置)

  手机上网现在已经被许多手机用户广泛使用,笔者谨希望这篇文章能对用手机上网的用户有所帮助。

如今,Google、雅虎或者Microsoft等公司几乎每周都会宣布或推出新的网络服务。渐渐地,隐藏在这些服务后面的互联网新轮廓显现出来,那就是Web 2.0。可是,等待我们的究竟是什么呢?

你可以把互联网想像成:一个人坐在一台功能有限的个人电脑前。在大部分情况下,这些电脑使用的都是微软的Windows操作系统。然后,电脑上加了一根传输数据的线,线的另一端,就是一个名为互联网的巨大的数据库。

但在Forrester Research公司的分析师查伦?李(Charlene Li)眼里,互联网却是另外一个样子:每一个网民拥有的其实不是一台小电脑,而是一个运算能力和存储容量都无比巨大的计算机。这是她在最近在旧金山举行的“第二届Web 2.0大会”上提出的观点。


视角不同,说法不同。如O’Reilly专业出版社老板Tim O’Reilly就是以操作系统来评定的。在他眼里,只有Linux用户,每一个上网浏览的人,都依赖这个开放式系统,因为Google和雅虎这两个最大的互联网搜索引擎都是利用Linux系统来控制他们的天下的。在Tim O’Reilly看来,与Linux相比,Windows的作用几乎不算什么:和我们通过宽带得到的网络功能相比,个人电脑充其量也就是一个带鼠标的显示器而已。

读者一同编写

在查伦?李和O’Reilly看来,不仅是互联网等同于数据库的概念早已过时,用于有了博客(网络日志)、维基、公共书签等的问世,创造信息者和消费信息者之间已经不存在什么界限了。假如把互联网比作图书馆的话,那就是每一个读者都可以参与创作,也就是不仅是全民上网,而且是全民织网。

这一切,并不是在一夜之间就形成的,因此,对推广Web 2.0这个概念发挥了关键作用的O’Reilly在一篇文章中说,这是互联网的演变,而不是革命。但这一演变过程的欣欣活力现在也开始令开发互联网的鼻祖们坐不安宁了。

思想观点问题

无论是雅虎、微软、eBay,还是亚马逊、Google、美国在线,所有在网络行业大名鼎鼎的公司都首次参加了今年的Web 2.0大会,大部分都是总裁亲自光临,与诸多试图凭独特创意打开Web 2.0市场的新创公司(Startups)会聚一堂。查伦?李说:“这些大公司已经意识到,它们必须顺应潮流。”

可是,Web 2.0究竟是什么呢?一个明天就可以下载的新版本吗?其定义随其定义者的兴趣,多种多样。用O’Reilly的话来说,它“更像是一种思想观点”。

对雅虎总裁特里?塞梅尔(Terry Semel)来说,就是由网民打造互联网,“社会性软件”(social software,又称社会软件)是其关键词,比如他们推出的“雅虎360度”:1.6亿用户将可通过便捷的使用界面,利用网上的所有表达方式——图片、录像、声音、文字,自己组织感兴趣的内容,自己互相联网。

精简软件

就是在软件开发领域也有观念转变的迹象,如37Signals公司的Jason Fried就试问道,为什么办公软件要具备那么多几乎没几个人用到的功能呢?他提出“精简软件”(Lightweight Applications)的概念,即功能少但却足够一般使用的“苗条”型软件程序。

这种运作模式的基础是开放源代码技术。利用开放源代码技术,分散在全球各地的灵活的小型编程小组可以一起“编织”同一个程序的不同模块。“精简软件”也将如此运作,只不过这里生成的不是一成不变的程序,而是每个人都可以像搭积木一样,从诸多小程序中,搭建自己需要的软件。

以Gmail为例

像查伦?李这样的分析师认为,这种软件组合新方式将会给传统的桌面软件带来不可低估的竞争。她提问道,假如人们可以从网上得到越来越多的免费服务项目,谁还愿意再去花大钱,给自己桌上的电脑买软件呢?在她看来,像“google earth”或“yahoo maps”等免费地图服务只是一个全新的“软件服务”浪潮的开始。

Google就已经以其Gmail电子邮件服务为此提供了一个例子:这个在线电子邮件服务像所有其它互联网电邮服务一样,只要能上网,到处都可用,但它也同时包括了桌面电子邮件程序如Outlook的所有邮件管理功能。

网络服务,前景广阔,难怪大部分博客对Web 2.0大会的评语都是极其热烈的。许多人想起上世纪90年代初互联网初露头角时代的奋发气氛,如今价值亿万的企业当时还只是些主意怪怪的新创公司(Startups)。但这样的公司今天继续生存的机会不大,因为它们的主意一旦有望成功,马上就会被大公司并购。

网络服务大有金子可淘。这个金子,比如说可以是信息,但对其合法性颇有争议,比如Gmail,在德国隐私保护者看来恐怕就与德国基本法(即宪法)中第10条保护电信秘密的规定相违。Gmail的?版就搜索用户的所有电子邮件,以打出和邮件内容相关的广告。就是雅虎的赚钱方式实际上也是根据用户信息打出量身定做的广告的。

“您支付网络服务的不是金钱,而是您个人的信息”,查伦?李这样说,并补充道,她认为这些公司是会慎重对待用户个人信息的,完全令人信赖,毕竟,信赖是它们最重要的资本。她说,完全可以想像,Google有朝一日也会根据相应的询问,在通过其IP电话服务进行的通话内容中搜索关键词。网络公司知道你的一切。人们当然也可以这样想像互联网。

      今天心情很糟糕

      被人怀疑能力是很没有面子的事,特别是自己确实不会,连争辩的底气都没有,只能低头认栽。心情糟糕到了极点,可怜却连安慰自己的言语都找不到,因为实在找不到还口的理由,毕竟是自己不行嘛。因为不想自己骗自己,所以只能眼睁睁地看着自己的心被越揪越紧,痛。

      真受不了这种无奈与郁闷,连大吵一架的权利都没有,只能在心底一遍又一遍地狠狠地说着:“我要变强,我要真真正正、脚踏实地做事情,我一定要变强、变强”说着说着,鼻子就会一阵阵地发酸……讨厌,真没出息。

      本以为自己已经在变成熟,没想到其实都是自我膨胀的幻影,记录下自己的弱点:

1、遇到困难容易逃避、后退、放弃,不能勇敢地面对困难和挑战,结果只能让事情变得越来越糟糕。

2、懒惰,贪图享乐,享受是应该的,但应该清楚什么才是更重要的,不能因为享乐而耽误了正事。

3、缺乏勇气,面对机会和挑战,不能理智分析,勇往直前。没有大胆尝试的勇气与自信。

      所有,这些弱点,我都要克服,勇敢而坚强地去克服掉。

1、 情况不同

一只小猪、一只绵羊和一头乳牛,被关在同一个畜栏里。有一次,牧人捉住小猪,牠大声号叫,猛烈地抗拒。绵羊和乳牛讨厌牠的号叫,便说:「他常常捉我们,我们并不大呼小叫。小猪听了回答道:「捉你们和捉我完全是两回事,他捉你们,只是要你们的毛和乳汁,但是捉住我,却是要我的命呢!」
立场不同、所处环境不同的人,很难了解对方的感受;因此对别人的失意、挫折、伤痛,不宜幸灾乐祸,而应要有关怀、了解的心情。

2、 自己

小蜗牛问妈妈:为什么我们从生下来,就要背负这个又硬又重的壳呢?
妈妈:因为我们的身体没有骨骼的支撑,只能爬,又爬不快。所以要这个壳的保护!
小蜗牛:毛虫姊姊没有骨头,也爬不快,为什么她却不用背这个又硬又重的壳呢?
妈妈:因为毛虫姊姊能变成蝴蝶,天空会保护她啊。
小蜗牛:可是蚯蚓弟弟也没骨头爬不快,也不会变成蝴蝶他什么不背这个又硬又重的壳呢?
妈妈:因为蚯蚓弟弟会钻土, 大地会保护他啊。
小蜗牛哭了起来:我们好可怜,天空不保护,大地也不保护。
蜗牛妈妈安慰他:「所以我们有壳啊!」
我们不靠天,也不靠地,我们靠自己。

3、 鲨鱼与鱼

曾有人做过实验,将一只最凶猛的鲨鱼和一群热带鱼放在同一个池子,然后用强化玻璃隔开,最初,鲨鱼每天不断冲撞那块看不到的玻璃,耐何这只是徒劳,它始终不能过到对面去,而实验人员每天都有放一些鲫鱼在池子里,所以鲨鱼也没缺少猎物,只是它仍想到对面去,想尝试那美丽的滋味,每天仍是不断的冲撞那块玻璃,它试了每个角落,每次都是用尽全力,但每次也总是弄的伤痕累累,有好几次都浑身破裂出血,持续了好一些日子,每当玻璃一出现裂痕,实验人员马上加上一块更厚的玻璃。后来,鲨鱼不再冲撞那块玻璃了,对那些斑斓的热带鱼也不再在意,好像他们只是墙上会动的壁画,它开始等着每天固定会出现的鲫鱼,然后用他敏捷的本能进行狩猎,好像回到海中不可一世的凶狠霸气,但这一切只不过是假像罢了,实验到了最后的阶段,实验人员将玻璃取走,但鲨鱼却没有反应,每天仍是在固定的区域游着它不但对那些热带鱼视若无睹,甚至于当那些鲫鱼逃到那边去,他就立刻放弃追逐,说什么也不愿再过去,实验结束了,实验人员讥笑它是海里最懦弱的鱼。
可是失恋过的人都知道为什么,它怕痛。

4、 神迹

法国一个偏僻的小镇,据传有一个特别灵验的水泉,常会出现神迹,可以医治各种疾病。有一天,一个拄着拐杖,少了一条腿的退伍军人,一跛一跛的走过镇上的马路,旁边的镇民带着同情的回吻说:「可怜的家伙,难道他要向上帝祈求再有一条腿吗?」这一句话被退伍的军人听到了,他转过身对他们说:「我不是要向上帝祈求有一条新的腿,而是要祈求祂帮助我,叫我没有一条腿后,也知道如何过日子。」
试想:学习为所失去的感恩,也接纳失去的事实,不管人生的得与失,总是要让自已的生命充满了亮丽与光彩,不再为过去掉泪,努力的活出自己的生命。

5、 钓竿
有个老人在河边钓鱼,一个小孩走过去看他钓鱼,老人技巧纯熟,所以没多久就钓上了满篓的鱼,老人见小孩很可爱,要把整篓的鱼送给他,小孩摇摇头,老人惊异的问道:「你为何不要?」小孩回答:「我想要你手中的钓竿。」老人问:「你要钓竿做什么?」小孩说:「这篓鱼没多久就吃完了,要是我有钓竿,我就可以自己钓,一辈子也吃不完。」
我想你一定会说:好聪明的小孩。错了,他如果只要钓竿,那他一条鱼也吃不到。因为,他不懂钓鱼的技巧,光有鱼竿是没用的,因为钓鱼重要的不在<钓竿>,而在<钓技>有太多人认为自己拥有了人生道上的钓竿,再也无惧于路上的风雨,如此,难免会跌倒于泥泞地上。就如小孩看老人,以为只要有钓竿就有吃不完的鱼,像职员看老板,以为只要坐在办公室,就有滚进的财源。

1、父子二人经过五星级饭店门口,看到一辆十分豪华的进口轿车。儿子不屑地对他的父亲说:「坐这种车的人,肚子里一定没有学问!」父亲则轻描淡写地回答:「说这种话的人,口袋里一定没有钱!」

(你对事情的看法,是不是也反映出你内心真正的态度?)

2、晚饭后,母亲和女儿一块儿洗碗盘,父亲和儿子在客厅看电视。突然,厨房里传来打破盘子的响声,然后一片沉寂。是儿子望着他父亲,说道:「一定是妈妈打破的。」「你怎么知道?」「她没有骂人。」

(我们习惯以不同的标准来看人看己,以致往往是责人以严,待己以宽。)

3、有两个台湾观光团到rib伊豆半岛旅游,路况很坏,到处都是坑洞。其中一位导游连声抱歉,说路面简直像麻子一样。说而另一个导游却诗意盎然地对游客说:诸位先生女士,我们现在走的这条道路,正是赫赫有名的伊豆迷人酒窝大道。」

(虽是同样的情况,然而不同的意念,就会产生不同的态度。思想是何等奇妙的事,如何去想,决定权在你。)

4、同样是小学三年级的学生,在作文中说他们将来的志愿是当小丑。中国的老师斥之为:「胸无大志,孺子不可教也!」带外国的老师则会说:「愿你把欢笑带给全世界!」

(身为长辈的我们,不但容易要求多于鼓励,更狭窄的界定了成功的定义。)

5、在故宫博物院中,有一个太太不耐烦地对她先生说:「我说你为甚么走得这么慢。原来你老是停下来看这些东西。」

(有人只知道在人生的道路上狂奔,结果失去了观看两旁美丽花朵的机会。)

6、妻子正在厨房炒菜。丈夫在她旁边一直唠叨不停:慢些。小心!火太大了。赶快把鱼翻过来。快铲起来,油放太多了!把豆腐整平一下!「哎u」妻子脱口而出,「我懂得怎样炒菜。」「你当然懂,太太,」丈夫平静地答道:「我只是要让你知道,我在开车时,你在旁边喋喋不休,我的感觉如何。」

(学会体谅他人并不困难,只要你愿意认真地站在对方的角度和立场看问题。)

7、一辆载满乘客的公共汽车沿着下坡路快速前进着,有一个人後面紧紧地追赶着这辆车子。一个乘客从车窗中伸出头来对追车子的人说:"老兄!算啦,你追不上的!""我必须追上它,"这人气喘吁吁地说:"我是这辆车的司机!"

(有些人必须非常认真努力,因为不这样的话,後果就十分悲惨了!然而也正因为必须全力以赴,潜在的本能和不为人知的特质终将充份展现出来。)

8、甲:「新搬来的邻居好可恶,昨天晚上三更半夜、夜深人静之时然跑来猛按我家的门铃。」
乙:「的确可恶!你有没有马上报警?」
甲:「没有。我当他们是疯子,继续吹我的小喇叭。」

(事出必有因,如果能先看到自己的不是,答案就会不一样在你面对冲突和争执时,先想一想是否心中有亏,或许很快就能释怀了)

9、某日,张三在山间小路开车,正当他悠哉地欣赏美丽风景时,突然迎面开来一辆货车,而且满囗黑牙的司机还摇下窗户对他大骂一声:"猪!"
张三越想越纳闷,也越想越气,於是他也摇下车窗回头大骂:"你才是猪!"
才刚骂完,他便迎头撞上一群过马路的猪。

(不要错误的诠释别人的好意,那只会让自己吃亏,并且使别人受辱。在不明所以之前,先学会按捺情绪,耐心观察,以免事後生发悔意。)

10、小男孩问爸爸:"是不是做父亲的总比做儿子的知道得多?"
爸爸回答:"当然啦!"
小男孩问:"电灯是谁发明的?"
爸爸:"是爱迪生。"
小男孩又问:"那爱迪生的爸爸怎麽没有发明电灯?"

(很奇怪,喜欢倚老卖老的人,特别容易栽跟斗。权威往往只是一个经不起考验的空壳子,尤其在现今这个多元开放的时代。)

11、小明洗澡时不小心吞下一小块肥皂,他的妈妈慌慌张张地打电话向家庭医生求1助。医生说:"我现在还有几个病人在,可能要半小时後才能赶过去。"
小明妈妈说:"在你来之前,我该做甚麽?"
医生说:"给小明喝一杯白开水,然後用力跳一跳,你就可以让小明用嘴巴吹泡泡消磨时间了。"

(take it easy,放轻松放轻松些,生活何必太紧张?事情既然已经发生了,何不坦然自在的面对。担心不如宽心,穷紧张不如穷开心。)

12、一把坚实的大锁挂在大门上,一根铁杆费了九牛二虎之力,还是无法将它撬开。钥匙来了,他瘦小的身子钻进锁孔,只轻轻一转,大锁就"啪"地一声打开了。
铁杆奇怪地问:"为什麽我费了那麽大力气也打不开,而你却轻而易举地就把它打开了呢?"
钥匙说:"因为我最了解他的心。"

(每个人的心,都像上了锁的大门,任你再粗的铁棒也撬不开。唯有关怀,才能把自己变成一只细腻的钥匙,进入别人的心中,了解别人。)

附加:

 有一个愣头愣脑的流浪汉,常常在市场里走动,
许多人喜欢开他的玩笑,并且用不同的方法作弄他。
其中有个一个大家常用的方法,
就是在手掌上放一个5元和10元的硬币,由他来挑选,
而他每次都选择5元的硬币。大家看他傻乎乎的,
连5元和10元都分不清楚,都捧腹大笑。
每次看他经过,都一再的以这个手法来取笑他。
过了一段时间,一个有爱心的老妇人,就忍不住问他:
「你真的连5元和10元都分不出来吗?」
流浪汉露出狡诘的笑容说:
「如果我拿10元,他们下次就不会让我挑选了。」

默想:当人自以为聪明时,其实正显示出愚昧和无知。
让我们多以柔和谦卑的态度与人相处,那才真正是智者的作为。

●「多数人的失败不是因为他们的无能, 而是他们的心智不专一。」——吉鲁得

有一个外科医生告诉学生:「当一个外科医生需要2项重要的能力:
第一、不会反胃,第二、观察力要强。」
接着,他伸出一只手指,沾入一碟看来令人作呕的液体中,
然后张口舔舔手指。他要全班学生照做,他们只好硬着头皮照做一遍。
医生颔首一笑说:
「各位,恭喜你们通过了第一关的测验。不幸的是,第二关你们都没过,
因为你们没注意到我舔的手指头,不是我伸入碟中的那个手指。」

默想:你有没有仔细而认真的观察,现在从事的工作是否有不佳之处?
即时调整,永远不晚。一个认真的人也必是一个智慧的人。

●「没有乌云,没有暴风雨,便没有美丽的彩虹。」 ——芬生

有一朵看似弱不禁风的小花生长在一棵高耸的大松树下。
小花非常庆幸有大松树成为它的保护,为它挡风挡雨,每天可以高枕无忧。
有一天,突然来了一群伐木工人,两三下功夫,就把大树整个锯了下来。
小花非常伤心,痛苦道:「天啊!我所有的保护都失去了;从此那些嚣张
的狂风会把我吹倒,滂沱的大雨会把我打倒!」
远处的另一棵树安慰它说:「不要这么想,刚好相反,少了大树的阻挡,
阳光先会照耀你,甘霖会滋润你;你弱小的身躯将长得更茁壮,
你盛开的花瓣将一一呈现在灿烂的日光下。
人们会看到你,并且称赞说,这朵可爱的小花长的真美丽啊!

默想:当失去一些以为可以长久依靠的东西,
自然会有难过及割舍的痛苦,但其中却隐藏着无限的祝福和机会。
日后回首时,你才惊讶自己成长的痕迹,是那么清晰明显,
甚至令人满心喜悦的。

●没有失败,只有暂时停止成功。

如果你已经为人父母,当你的孩子正在学习走路时,你会给他几次机会?
你会在他跌倒10次之后,让他改座轮椅吗?
还是只给他二十次学走的机会,若学不会走路就要他放弃?
或者身边有五十个人叫嚣着劝你放弃,你就决定让他做轮椅呢?
我想你的答案是:不会。

的确,当我问每一位父母,会给你的孩子几次机会呢?
他们都说:我会给他无数次机会,直到他站起来,学会走路为止!
是的,一直坚持到底者,最终都会站起来。

为什么许多父母只给孩子一次联考的机会?
为什么常用失望的口气告诉孩子不适合某种行业,要求他转行呢?
而许多人竟也因为没有坚定的信念,一遇挫折就认为自己能力不足,
因此放弃了他们的理想。

其实,凡事没有失败,只有暂时停止成功。

●重要的不是发生了什么事,而是要做那些事来改善它。

佛家说逆境是「增上缘」,可遇不可求。
遇到困难时,重要的不是发生了什么事,而是你即将用什么态度来面对,
做些什么事来改善它。

●我对我的生命完全负责。

成功者对事情百分之百负责,负责的定义是永远握有主控权。
如果不能主控生命,很可能一生随波逐流。
对生命负全责者,他为自己而活,并学习替别人着想。
 

2005年11月21日

      暖气终于、终于修好了,新买的暖气也装好了,热乎乎的,好喜欢。

      屋里大约能有18、9度的样子,挺舒服的,终于不用再围着电暖气生活了,晚上的时候,过一会儿,我和LG就会一起跑去暖气那去“暖和”一下,把手放在暖气片上,暖暖的,好舒服,感觉好幸福,好有成就感,嘿嘿,瞧,我们也能把家弄得好好的。

2005年11月19日

摘要

  J2EE编程正在变得越来越复杂。J2EE已经发展为一个API、复杂化的编程和配置的复杂网络。为了应对这种复杂性,新的框架和方法不断涌现。这些框架高度依赖于一个称为IoC(Inversion of Control,反向控制)的概念。本文将探讨这种方法的一些特性和优点,因为这种方法与J2EE编程相关,而且可以使J2EE编程变得更轻松。

简介

  马克·吐温的一句话常被引用:“……关于我死亡的报道是一种夸张。”现在已经出现了很多关于.Net的流言,以及认为J2EE API的复杂性无法克服和EJB作为一种组件架构即将灭亡的流行极客(geek)文化。从学术或者只是想像的立场来看,这没什么大不了的,但事实是J2EE/EJB API已经经历了一场达尔文式的进化。具有DCOM或CORBA项目经验的读者会明白我的意思。过去,人们都乐于听闻EJB组件模型的美好前景。实际情况是,人们在与J2EE相关的各个方面都投入巨大。宣布抛弃以前的所有工作并重新组织,这种想法看起来也许有理,但是它并没有建立在良好的业务洞察力之上。EJB继续发展,而术语、实践和框架也随之涌现(spring up),它们弥补了J2EE API的不足。我说的不是“Spring出现(up)”,对吧?

  我是一名顾问,职责是帮助构建大型的分布式应用程序,而且通常是J2EE应用程序。因此,我有机会亲历许多项目的整个生命周期。另外我还能够将我从一个刚刚完成的项目中刚刚学到的东西直接带入一个全新的项目。从某种意义上说我的“自然选择”过程加快了。我可以说最近Spring(更具体地说就是IoC,即反向控制)已经越来越多地融入到我的项目中了。在本文中,我将从支持或增强J2EE项目的角度来探讨Spring。更确切地讲,Spring框架能够标准化许多J2EE最佳实践,还能同类化(homogenize)许多无处不在的J2EE模式。接下来我们将浏览Spring庞大体系中的一小部分内容,重点介绍(依我浅见)能够帮助改进J2EE应用程序的功能。

IoC简介

  一般来说,IoC是一种管理类之间关联的技术。没错,就这么简单!任何人都不是孤立的,对于各个对象来说也是如此。应用程序中的对象是相互依赖的。通过编程方式来表现这种依赖性通常既冗长又容易出错。好的IoC框架将声明式地(通过一个XML配置文件)而不是编程式地(这种方式的可靠性较差)——串连起应用程序之间的相互依赖性。

  自由使用接口是IoC开发的一个主要方针。接口编程大大提高了应用程序的灵活性,从而增强了声明式的关联。接口实现是通过IoC配置在运行时声明的,这样就能够在不影响或少影响实际应用程序代码的情况下“重建(rewire)”关联。这在各种IoC框架中是反复提及的一个主题,一般而言,也是应该遵循的良好实践。

一个小例子

  我喜欢通过例子来更快地理解概念。下面就是运用了IoC的一组例子;您将看到,这些例子的复杂性是逐递增的。大多数人在一开始使用IoC容器时都是利用其依赖注入(inject dependency)功能——即,声明式地将对象关联起来。利用IoC有助于创建更整洁的代码,如有必要重建对象之间的关联,一般来说对于这些代码也会更灵活、更容易。IoC的优点远不止依赖注入,而其扩展功能确是以依赖注入程序为起点的。

  我们将从构建简单的依赖注入例子开始。第一个例子用于阐明已经提及的两个概念。第一个概念是IoC在运行时构建和关联对象的能力,第二个是与接口编码相结合而产生的灵活性。首先假定架构师递交了图1所示的UML。

 


图1. 接口可插性

  这个小例子表示一个温度测量系统。几个传感器对象属于不同的类型,但都实现了ProtocolAdapterIfc接口,因此在将它们插入TemperatureSensor对象时,它们是可互换的。在需要TemperatureSensor时,系统中的某个实体必须知道要生成并与该传感器对象关联的ProtocolAdapterIfc的具体类型。在本例中,该传感器可基于命令行参数、数据库中的行或通过属性文件进行配置。本例还不足以造成挑战或展示一个复杂框架,但它足以阐明IoC基础。

  但是,想象一下:在一个相当复杂的应用程序中这种情况屡屡发生,而您还希望能动态地——至少要在外部——改变对象关联。假设有一个DummyProtocolAdapter,它总是返回42这个值,使用它来进行测试。为什么不提供一个单个的统一框架?——让开发人员能够依靠该框架,以一种一致的、外部配置的方式建立类之间的关联,并且不引起工厂单元素类(factory singleton classe)的异常增加。这听起来可能没什么大不了,但它要依赖于IoC的简单性。

  我们使用一个TemperatureSensor类,它与一个实现ProtocolAdapterIfc接口的类有关联。TemperatureSensor将使用该委托类来获得温度值。如UML图所示,在实现ProtocolAdapterIfc并且随后可用于该关联的应用程序中有若干个类。我们将使用IoC框架(在本例中是Spring)来声明要使用的ProtocolAdaperIfc的实现。Spring将在运行时建立关联。我们先来看XML代码,它将实例化TemperatureSensor对象并将一个ProtocolAdapterIfc实现与它关联起来。该代码如下所示:
<bean id="tempSensor"
class="yourco.project.sensor.TemperatureSensor">
  <property name="sensorDelegate">
    <ref bean="sensor"/>
  </property>
</bean>

<!– Sensor to associate with tempSensor –>
<bean id="sensor" class="yourco.project.comm.RS232Adapter"/>


  看了这些代码之后,对于其目的就应该非常清楚了。我们配置Spring来实例化TemperatureSensor对象,并将其与RS232Adapter相关联,作为实现ProtocolAdapterIfc接口的类。若想改变已经与TemperatureSensor关联的实现,惟一需要更改的就是sensor bean标记中的class值。只要实现了ProtocolAdapterIfc接口,TemperatureSensor就不再关心关联了什么。

  将这应用于应用程序相当简单。我们必须先接入Spring框架,将它指向正确的配置文件,然后根据名称向Spring索取tempSensor对象的实例。下面是相应的代码:

ClassPathXmlApplicationContext appContext =
  new ClassPathXmlApplicationContext(
  new String[]
  { "simpleSensor.xml" });
BeanFactory bf = (BeanFactory) appContext;
TemperatureSensor ts = (TemperatureSensor)
  bf.getBean("tempSensor");
System.out.println("The temp is: "+
  ts.getTemperature());


  可以看出,这些代码并不是非常难。首先是启动Spring并指定要使用的配置文件。接下来根据名称(tempSensor)引用Bean。Spring使用这样一种机制:基于simpleSensor.xml文件的描述创建该对象并与其他对象关联。它用于注入依赖性——在本例中,通过将它作为一个参数传递给sensorDelegate()方法而实例化RS232Adapter对象并将其与TemperatureSensor对象关联。

  比较起来,使用编程式Java完成这一任务也不是很难。如下所示:

TemperatureSensor ts2 = new TemperatureSensor();
ts2.setSensorDelegate(new RS232Adapter());

  纯粹主义者或许会认为实际上这是更好的方法。代码行数少,并且可读性可能更强。确实如此,但这种方法的灵活性要小得多。

可以随意换入和换出不同层中不同对象的不同实现。例如,若Web层中的组件需要来自新业务对象的额外的功能,您只需将该业务对象与Web层对象相关联,就像上面TemperatureSensor例子中的做法。它将被“注入”到Web对象中以随时使用。
能够重新配置整个应用程序的结构,意味着可以轻松更改数据源。比如说,或者为不同的部署场景创建不同的配置文件,或者为测试场景创建更有用的、不同的配置文件。在测试场景中可能会注入实现接口的模拟对象,而不注入真正的对象。稍后我们将介绍一个这样的例子。
  上面所述的例子可能是依赖注入的最简单形式。利用相同的策略,我们不仅能够关联不同的类,还能够在类中安装属性。诸如字符串、整数或浮点数之类的属性,只要具有JavaBean样式的存取器,就可以通过Spring配置文件将它们注入类中。我们还可以通过构造函数来创建对象和安装属性或bean引用。其语法只比通过属性进行设置稍稍复杂一些。

  所有这一切都是利用一种灵活的声明性配置完成的。无需更改代码,建立依赖关联的所有艰难任务都由Spring来完成。

Spring–标准化的定位器模式

  我一直将服务定位器模式视作良好的J2EE规范的主要组成部分。对于不熟悉这一术语的人来说,可以这样理解它:我们一般认为典型的J2EE应用程序由若干层组成。通常有Web层、服务层(EJB、JMS、WS、WLS控件)以及数据库。一般来说,完成某一请求所需的“查找”服务中都包含了一些方法。Service Locator(服务定位器)模式认为,将这些方法包装在某种隐藏了生成或查找给定服务的复杂性的工厂类中是一个好主意。这减少了JNDI或只会造成Web层操作类混乱的其他服务产品代码的增加。在Spring出现以前,这通常是由经过考验证明可靠的(tried-and-true)Singleton类来实现的。Singleton/Locator/Factory模式可以描绘为:

 


图2. 定位器模式的顺序图

  这是对散布在整个Web控制器代码中的增加的JNDI查找代码的一个巨大改进。它被巧妙地隐藏在工厂内部的协作类中。我们可以使用Spring来改进这一术语。此外,该解决方案将适用于EJB、Web services、异步JMS调用,甚至还有基于WLS控件的服务。由Spring实现的这种定位器模式的变体考虑了业务服务之间的一些抽象化和同质性。换句话说,Web控制器的开发人员真的可以不考虑他们所使用的服务的种类,一个类似于“WLS控件”但是更通用的概念。

  IoC框架大大改进了这种模式的效用,而且实际上废除了复杂而特殊的singleton代码来实现它。通过借用上例中引入的概念,我们实际上无需额外代码便能构建一个非常强大且无处不在的Service Locator模式。为此,在一开始有一个简单的要求,即Web操作的开发人员应专门处理实现接口的那些事情。这基本上已经通过EJB编程实现,但并不是说Web操作的开发人员处理的服务必须通过EJB来实现。它们可能只是普通Java对象或Web services。要点是应当通过接口(这样实现能够换入换出)来编写服务程序,并且运行时配置能够由Spring处理。

  Spring之所以非常适合于Service Locator模式,是因为它或多或少能够统一地处理不同类型的对象。通过少许的规划和大量使用IoC,我们多少都能够以一种通用方式来处理大多数对象,而不用管它们的特性(EJB、POJO等等)如何,并且不会引起Singleton工厂类的增加。这使Web层编程变得更加轻松和灵活。

  我们先来看一个关于这种模式如何应用于EJB的例子。我们都知道使用EJB可能是最复杂的方法,因为要将一个活动的引用引入EJB要做很多工作。若使用Spring,建议用EJB接口扩展非特定于EJB的业务接口。这样做有两个目的:保持两个接口自动同步,以及帮助保证业务服务对非EJB实现是可交换的,以便进行测试或清除(stubbing)。我们可以利用Spring固有的实用工具来定位和创建EJB实例,同时为我们处理所有难以处理的工作。相应代码如下所示:
<bean id="myBizServiceRef"
         class="org.springframework.ejb.access.
         LocalStatelessSessionProxyFactoryBean">
  <property name="jndiName">
    <value>myBizComponent</value>
  </property>
  <property name="businessInterface">
    <value>
      yourco.project.biz.MyBizInterface
    </value>
  </property>
</bean>

  接下来可以检索bean并开始使用它,方法如下:

  MyBizInterface myService = bf.getBean("myBizServiceRef");

  这将返回Spring动态创建并包装了底层目标(在本例中是一个本地EJB实例)的一个对象。这种方法非常好,因为它完全隐藏了我们在处理EJB这一事实。我们将与一个实现简单业务接口的代理对象交互。Spring已经基于“真正的”业务对象考虑周到地动态生成了该对象。所包装的对象当然就是Spring定位和检索引用所要获得的本地EJB。此外,您还会注意到,这种代码形式与前面用于检索tempSensor对象的代码完全相同。

  那么如果我们改变主意,想用普通Java对象来实现业务组件;或者可能在测试中,我们想用一个返回“固定(canned)”响应的已清除(stubbed)对象来替换重量级EJB,该怎么做呢?利用IoC和Spring,通过更改Spring上下文文件就可轻而易举地实现这些目标。我们只需使用更常规一点的东西(如我们在第一个Spring例子中所看到的)来替换EJB代理的连接即可:

<bean id="myBizServiceRef"

class="yourco.project.biz.MyStubbedBizService">

</bean>

  请注意,我只更改了Spring框架所返回的内容的细节,没有更改bean id。最后的结果是业务对象的解决方案未变;它看上去和以前完全一样:

MyBizInterface myService =

bf.getBean("myBizServiceRef");

  最大的区别显然是实现该业务接口的对象现在由一个普通Java对象(POJO)支持,并且只是该接口的一个已清除(stubbed)版本。这给单元测试或改变业务服务的特性带来了极大方便,而对客户端代码的影响很小。

使用Spring来标准化异常

Spring的一大贡献是“模板化”代码块。这在纯JDBC编程中表现得最为明显。我们都曾写过具有下述功能的代码:

创建一个数据库连接,可以的话从某个池创建。
构造一个查询字符串并提交。
迭代结果并将数据封送到域对象中。
处理不同阶段出现的大量异常。
确保记得编写finally代码块以关闭连接。
  但是各处的这种代码往往都会或多或少地有点“样板化”。一般来说这是有害的,不仅因为不需要的代码会增加,还因为有些东西可能会遗漏,如非常重要的关闭连接,如果没有实现它,可能导致数据资源池的泄漏。

  虽然我敢肯定我们都曾多次写过这类“样板”代码,但是将Spring方法和直接的JDBC实现对照来看,其结果将会有趣而又对比鲜明。“传统”的JDBC实现可能如下:

Connection con = null;
try
{
  String url = "jdbc://blah.blah.blah;";
  con = myDataSource().getConnection();
  Statement stmt = con.createStatement();
  String query = "SELECT TYPE FROM SENSORS";
  ResultSet rs = stmt.executeQuery(query);
  while(rs.next()){
    String s = rs.getString("TYPE);
    logger.debug(s + "   " + n);
  }
} catch(SQLException ex)
{
  logger.error("SQL ERROR!",ex);
}
finally
{
  con.close();
}
  对于该方法要做一些说明。首先,它是有效的!该代码绝对不会出现任何错误。它会连接到数据库,并从‘SENSOR’表获取所需的数据。该方法的基本问题源于缺乏抽象化。在大型应用程序中,必须反复剪切和粘贴这段代码,或者至少会出现类似的其他情况。较大的问题在于它依赖于编程人员去做“该做的事”。我们都知道,不管数据库操作的结果是什么,都必须用finally语句来关闭该数据库。有时我们忘记做该做的事。我和所有人一样感到内疚!

  编写一个小框架来解决这一问题会非常容易。我相信大家也都曾这样做过,但为何不让Spring帮我们处理这一问题呢?我不敢保证我能想出一个更整洁、更优雅的解决方案。我们来看Spring框架是如何处理这种样板JDBC场景的。

  Spring支持各种各样的JDBC、Hibernate、JDO和iBatis模板。模板采用样板概念,并将它转换为合法的编程术语。例如,下面的代码片断封装了上面列出的各个步骤:

DataSource ds = (DataSource) bf.getBean("myDataSource");
JdbcTemplate temp = new JdbcTemplate(ds);
List sensorList = temp.query("select sensor.type FROM sensors",
  new RowMapper() {
     public Object mapRow(ResultSet rs, int rowNum) throws SQLException;
        return rs.getString(1);
     }
  });

  这段简短的代码消除了JDBC编程的冗长,代表了前面提及的样板的思路。请注意我们使用了Spring的IoC来查找该查询的数据源。Spring还支持对已检查异常使用未检查异常;因此许多已检查的JDBC异常会重新映射到通常更有用而且更友好的未检查异常层次结构中。在Spring的上下文文件中配置该数据源类似于下面代码:

<bean id="myDataSource"
  class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName">
    <value>org.gjt.mm.mysql.Driver</value>
  </property>
  <property name="url">
    <value>jdbc:mysql://romulus/sensors</value>
  </property>
  <property name="username">
    <value>heater</value>
  </property>
  <property name="password">
    <value>hotshot</value>
  </property>
</bean>

  在本例中,我们利用Apache commons工具箱配置了一个基本数据源。但并不是说我们只能使用它。我们可以改变配置,使用在JNDI中配置并装载的数据源。Spring提供了一些实用工具和IoC功能以配置和返回存储在JNDI中的对象。例如,若想配置并使用与JNDI上下文关联的数据源,则可以输入以下代码,替换先前的数据源配置:

<bean id="myDataSource"
  class="org.springframework.jndi.
  JndiObjectFactoryBean">
  <property name="tempSensorDS">
    <value>ConnectionFactory</value>
  </property>
</bean>

  该代码突出了Spring所提供的关于表格的测试灵活性。该代码可以在“容器内”运行(从JNDI查找数据源),经过细微的改动之后也可在“容器外”运行。

  虽然模板化机制仅适用于某些特定场合,但我们可以泛化这一概念,将它应用于更广泛的场合。例如,一种将已检查异常转变为未检查异常、此外还可能为异常处理提供一些中间或统一的异常处理策略的机制将会很有帮助。我们还可以使用该机制将底层的基本异常“软化”得更合乎人意。我们可以将PacketFrameParityFaultException软化为CommunicationsUnreliableException。这个重新映射的较软的异常表示情况可能并不那么严重,重新请求也是可以的。

  Spring已经具备了一种类似于包装EJB调用(在最后一节介绍)的机制,但遗憾的是它并不具备任何“通用”的东西,至少在异常软化意义上是这样的。但Spring的确有一个非常健壮的AOP(面向方面编程)框架,我们可以用它来逼近这种行为。下面是一个关于这种软化适用领域的(公认的)精心设计的例子。

  我们再来看看本文前面已经开始探讨的一些概念。在第一节中我们介绍了一个基于远程传感器的小应用程序。现在我们继续探讨这个例子。我们将从一个简单的传感器接口开始介绍,该接口代码如下:

public interface ProtocolAdapterIfc
{
  public Integer getRemoteSensorValue()
    throws CommChecksumFault,
           CommConnectFailure,
           CommPacketSequenceFault;
}


  这并没有什么特别之处。显然实现该接口的任何人都会获得一个远程值并将它返回给调用者。在此期间调用者可能要面对某些可怕的灾难,该接口可能抛出的已检查异常就是例证。

  接下来我们来看该接口的一个实现程序。实现ProtocolAdapter的类是CarrierPigeon,其代码类似于:

public class CarrierPigeon
      implements ProtocolAdapterIfc
{
  private boolean isTired = true;
  private boolean canFlapWings = false;

  public Integer getRemoteSensorValue()
    throws CommChecksumFault,
           CommConnectFailure,
           CommPacketSequenceFault
    {
        if(isTired && !canFlapWings )
        {
          throw new
               CommConnectFailure("I’m Tired!");
        }
        return new Integer(42);
    }
}

  为简洁起见,这里省略了属性的getter和setter方法。当调用getRemoteSensorValue()时,CarrierPigeon方法将检查它是否使用过度以及它还能否执行。如果它确是使用过度并且不能执行,我们将无法获得任何值,必须抛出CommConnectionFailure,它是一个已检查异常。到现在为止,一直都还不错。但别高兴得太早了!我已经决定不让应用程序编程人员应对“疲劳的信鸽”。可以将它包装在某种对象中,并在达到目的之前捕获异常,但必须处理20种不同的传感器。此外,我更喜欢对这些东西进行适度透明地处理,随着对该系统了解的增多,或许还会更改异常处理程序中的逻辑。我想要的是一个兼容API,应用程序编程人员能够使用它,并且随着时间的推移能够改变或增强。我想模板化异常处理,并让应用程序编程人员能够处理软的未检查异常,而不是硬异常。Spring非常符合这种情况。下面是为此而使用的策略的要点:

定义一个较软的消除已检查异常的最小接口。这就是应用编程人员将使用的接口。
使用Spring AOP结构,开发一个客户机调用和目标对象调用之间的拦截器,在本例中是“信鸽”。
使用Spring安装该拦截器并运行。
  首先来看这个较软的接口:

public interface SensorIfc
{
  public Integer getSensorValue();
}


  请注意,在限定范围内可以重命名方法,使其更有意义。还可以消除已检查异常,就像这里所做的一样。接下来,也可能会更有趣的是我们想要让Spring将其注入调用栈的拦截器:

import org.aopalliance.intercept.MethodInterceptor;

public class SensorInvocationInterceptor
  implements MethodInterceptor
{
  public Object invoke(MethodInvocation
     invocationTarget) throws Throwable
  {
    // Return object reference
    Object o = null;

    // Convert it to the protocol interface type
    ProtocolAdapterIfc pai =
      (ProtocolAdapterIfc) invocationTarget.getThis();

    try
    {
      o = pai.getRemoteSensorValue();
    }
    catch (CommChecksumFault csf)
    {
      throw new SoftenedProtocolException(
       "protocol error [checksum error]: "
        + csf.getMessage());
    }
    catch (CommConnectFailure cf)
    {
      throw new SoftenedProtocolException(
        "protocol error [comm failure]: "
        + cf.getMessage());
    }
    catch (CommPacketSequenceFault psf)
    {
      throw new SoftenedProtocolException(
        "protocol error [message sequence error]"
        + psf.getMessage());
    }

    return o;
  }
}
  通过实现Spring MethodInterceptor接口并将该类插入Spring系统(稍后我们将进行讨论),我们将通过MethodInvocation参数获得实际的目标方法。这样就能提取我们想要的真正对象。请记住,我们更改了被调用者看作SensorIfc的接口,该接口使目标对象得以实现ProtocolAdapterIfc。我们这样做是为了简化调用,更是为了消除所有的已检查异常。该拦截器只调用用来捕获可能抛出的任何已检查异常、并用SoftenedProtocolException将它们重新包装的目标方法。实际上,我们只重新包装消息,但要做到心中有数。SoftenedProtocolException扩展了RuntimeException,后者无疑是一个未检查异常。该操作的最终结果是应用程序开发人员不必处理任何已检查异常。如果他们真想处理异常,他们只需(非强制地)处理一个:SoftenedProtocolException。不错吧!

  那么,如何才能让Spring完成这一切呢?答案就是要有正确的配置文件。我们现在就来看一看该配置文件,并了解其工作原理:

<!– TARGET OBJECT –>
<bean id="protocolAdapter"
  class="yourco.project.comm.CarrierPigeon">
  <property name="isTired">
    <value>true</value>
  </property≷
  <property name="canFlapWings">
    <value>true</value>
  </property≷
</bean≷

<!– INTERCEPTOR –>
<bean id="sensorInterceptor"
  class="yourco.project.springsupport.
  SensorInvocationInterceptor"/>

<!–WIRE EVERYTHING UP, HAND BACK TO THE USER–>
<bean id="temperatureSensorOne"
  class="org.springframework.aop.framework.
  ProxyFactoryBean">
  <property name="proxyInterfaces">
    <value>
yourco.project.interfaces.SensorIfc
    </value>
  </property>
  <property name="target">
    <ref local="protocolAdapter"/>
  </property>
  <property name="interceptorNames">
    <list>
      <value>sensorInterceptor</value>
    </list>
  </property>
</bean>

  我们逐节看这些代码时,可以看到它比我们前面看到的Spring配置文件要稍微复杂一些。“TARGET OBJECT”注释下面的第一节指定了要作为调用的目标对象的类。还记得拦截器曾将一个参数传入其中来表示目标对象吗?这就是其工作原理。基于该参数,Spring现在知道要将哪个对象传递进来。“INTERCEPTOR”下面的代码节是在目标方法之前调用的类。此时,如果目标类抛出已检查异常,要开始处理异常,并进行软化。Spring将它与目标类相关联。最后一节是将各种元素联系起来。用户要请求的bean位于bean键temperatureSensorOne下。ProxyFactoryBean将生成并返回一个代理类,它用来实现yourco.project.interfaces.SensorIfc接口。目标对象当然就是protocolAdapter bean,它由yourco.project.comm.CarrierPigeon的实例支持。只要用户调用代理的方法就插入并调用的拦截器位于bean键sensorInterceptor下,并由yourco.project.springsupport.SensorInvocationInterceptor支持。请注意,可使用多个拦截器,因为该属性是一个列表,因此可以将许多拦截器对象关联到方法调用中,这是一个非常有用的理念。

  运行该应用程序时,根据插入的CarrierPigeon值,我们可以看到一些有趣的行为。如果我们的CarrierPigeon没有使用过度并能执行,我们将看到这样的输出:

The sensor says the temp is:42

  显然“信鸽”没问题而且状况很好,并计算出温度为42。如果由于改变CarrierPigeon Spring节中的值,而造成“信鸽”使用过度或不能执行,我们将得到如下所示的结果:

yourco.project.exceptions.comm.SoftenedProtocolException: protocol
 error [comm failure]: I’m Tired!
  at yourco.project.springsupport.SensorInvocationInterceptor.invoke
 (SensorInvocationInterceptor.java:57)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed
    (ReflectiveMethodInvocation.java:144)
  at org.springframework.aop.framework.JdkDynamicAopProxy.invoke
 (JdkDynamicAopProxy.java:174)
  at .getSensorValue(Unknown Source)
  at yourco.project.main.Main.main(Main.java:32)


  在这种情况下,“信鸽”或者使用过度,或者不能执行,因此得到的结果是SoftProtocolException,并附带一条消息说明发生的情况:“I’m tired!”够酷吧!

  我希望人们能够开始了解Spring的强大及其多种功能。Spring框架悄然兴起,并成为开发人员的编程工具箱中的真正的“瑞士军刀”。Spring在实现让开发人员能够集中于应用程序的基本组成部分这一传说般的承诺方面做得不错,这也正是它得以大行其道的业务逻辑。Spring将您从J2EE中的一些错综复杂的方面中解放出来。Spring的强大在于它能够使大多数东西看起来就如同非常普通的Java对象一样,而不管它们的性质或来源如何。既然Spring自身肩负起创建、关联和配置的重担,那么开发人员要做的只是掌握使用对象而不是构造对象的方法。Spring就如同在杂货店购买的预煮的饭菜。您要做的只是决定想吃什么、把它带回家、加热,然后吃!

综合说明

  Spring是一个非常健壮的轻量级框架,它极好地弥补了J2EE/EJB环境的不足。Spring真正伟大的一点在于它不走极端。您可以以一种非常简单的方式开始使用Spring(正如我所做的那样),只是建立常见的关联,作为定位器模式的一种实现。稍后,您将发现其惊人的功能,并且很快对它的要求会越来越多。

  我所发现的一个惊人之处是Spring所提供的测试灵活性。现在人们对通过Junit进行单元测试日益重视,这增加了测试,而不是减少测试。J2EE容器的使用使测试极端复杂,以至于难以进行。这种困难局面源于业务逻辑和容器框架服务之间产生的耦合。借助于Spring的配置机制,使实现可以动态切换,这样就有助于将业务对象从容器中释放出来。我们已经看到,如果只想测试Web组件,将活动的EJB换成其代理或一个已清除的业务服务并不会造成太大影响。借助于JDBC或Hibernate数据访问对象,我们可以使用常见的简单JDBC、非XA的数据源连接来测试这些组件,并将它们无缝地换出,代之以健壮的基于JTA、JNDI的对应连接。结论是:如果代码易于测试,并因此测试得更多,那么质量必然会提高。

结束语

  本文粗略地概括介绍了IoC,并详细介绍了Spring。Spring框架具有许多功能,其中许多功能在本文中只是点到为止。从基本的依赖注入到复杂的AOP操作,这些组成了Spring的强大功能,这是它的主要优点之一。能够根据问题需要使用或多或少的IoC功能是一个极具吸引力的理念,我认为,这在通常错综复杂的J2EE编程领域也是颇受欢迎的。现在看完了这篇文章,我衷心地希望它能对您有所帮助,并可以应用到您的工作中。欢迎随时提供反馈!

参考资料

Spring——Spring的官方Web站点,有许多很棒的说明文档!
Implementing Transaction Suspension in Spring——更深入地探讨Spring中的事务支持