首页 > 代码库 > POJO应用框架:Spring与EJB3.0的比较

POJO应用框架:Spring与EJB3.0的比较

英文原文地址:
http://www.onjava.com/pub/a/onjava/2005/06/29/spring-ejb3.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43718_Spring_EJB.html
关键词: Spring EJB



艾伯特.爱因斯坦曾经说过:“一切都应该尽可能地简单,但是不能更简单。”确实如此,简化一门理论的基本假设,使我们可以专注于真正关键的地方,这正是一直以来对科学真理的追求。企业软件开发同样如此。

提供一个将复杂的事物(例如,事务、安全或持久性)对开发者进行隐藏的应用框架是简化企业软件开发的关键。一个设计良好的框架可以提高代码重用率、开发者的生产力及软件的质量。然而,现有J2EE1.4的EJB2.1框架被普遍认为设计差,且过于复杂。不满于EJB2.1的框架结构,Java开发者尝试了各种各样的中间件服务传递方法。最引人注目的是,以下两个框架引起了开发者极大兴趣并得到了大量正面的反馈。他们以未来企业Java应用所选框架的姿态展现。

        Spring框架虽然很流行但并不是一个标准的开源框架。它主要由Interface21 Inc开发和控制。Spring框架结构是基于依赖注入(Dependency Injection (DI))的设计模式。它可以独立或在现有的应用服务器上运行,而且大量地使用了xml配置文件

        EJB3.0是由Java Community Process (JCP)制订的标准框架,为所有主要的J2EE厂商支持。JBoss已经提供了试用版EJB3.0标准的开源或商业性质实现。EJB3.0充分利用了Java的注释

这两个框架结构都有一个共同核心设计理念:将中间件服务传递给耦合松散的POJOS (Plain Old Java Objects, 简单洁净Java对象)。 这样的框架利用截取执行上下文或在运行时将服务对象注入POJO来把应用服务“缠绕”到POJO。POJO本身并不关心这种“缠绕”,对这种框架结构也没有什么依赖。因此,开发者可专注于业务逻辑和脱离框架的POJO单元测试。除此之外, 由于POJO并不须要继承框架的类或实现其接口,开发者能够极其灵活地搭建继承结构和建造应用。

然而,在拥有同一理念的同时,两个框架结构使用不同的方式来传递POJO服务。许多书籍或文章都将Spring 或EJB3.0和EJB2.1做了比较,但是对Spring 和EJB3.0的比较并没有仔细研究过。在本文中,我将对Srping和EJB3.0框架背后的关键不同处进行考察,并讨论其优缺点。本文的观点也适用于其它更少为人知的框架,因为他们都是对“耦合松散的POJO”的设计。希望这篇文章可以帮助你选择适合你需求的最好框架。

厂商无关性
开发者选择Java平台其中最引人注目的理由之一:厂商无关性。EJB3.0正是一套设计为厂商无关的开放性标准。EJB3.0标准为所有企业Java社团里开源或商业性质厂商所开发和支持。它将开发者与应用服务器实现完全隔离。例如,JBoss的 EJB3.0实现基于Hibernate,Oracle的基于TopLink,但是开发者并不须要学习Hibernate- 或TopLink的具体API来使应用可在Jboss或Oracle上运行。厂商无关性使EJB3.0与现今其它POJO中间件框架区别开来。

但是,正如许多EJB3.0评论家迅速所指出的,在本文撰写时EJB3.0标准还没有到达一个最终版本。大概还有一到两年的时间EJB3.0才能广泛地为所有主要J2EE厂商所支持。即使你的应用服务器本身不支持EJB3.0,你仍然可以通过下载安装”内嵌的”EJB3.0产品来运行EJB3.0的应用。例如,JBoss的内嵌EjB3.0是开源产品且可以在任何J2SE5.0兼容的环境运行(例如, 在任何Java服务器上),此产品正处于软件测试阶段。其它厂商不久也将发布自己的内嵌EJB3.0产品,特别是针对标准中关于数据持久性的部分。

另一方面,Spring一直以来都是非标准的技术,在未来可预知的一段时间内这种情况将持续下去。虽然你可以在任何应用服务器上使用Spring框架,Spring应用会被锁入在Spring本身和你选择整合进Spring的具体服务中。

        Spring框架是一个开源项目,但同时它有一个XML格式的配置文件和编程接口。当然任何一个非标准的产品都会有这种“锁入”(lock-in)的情况,并不是Spring特有的。但Spring应用的长期生存能力仍然还得托Spring这个项目的福(或者是Interface21公司,它雇佣了大部分Spring核心开发人员)。除此之外,假如你用到任何一个具体的Spring服务,例如,Spring事务管理器或则Spring MVC,你也会被锁入到这些API里。
        Spring的应用对终端用户是不可知的。例如,对数据持久服务,Spring框架兼容不同的DAO和JDBC的模版帮助类,如Hibernate, iBatis, 和 JDO。所以假如你需要为spring应用切换在数据持久化服务(例如从JBDC到Hibernate),你需要修改你的代码以适合新的模版帮助类。

服务整合
从一个很高的角度上看,Spring框架处于应用服务器和服务库的上方。服务整合的代码(如,数据访问模板和帮助类)属于框架,并暴露于应用开发者。相反,EJB3.0框架与应用服务器高度整合,服务整合代码也包装在一个标准接口后面。

因此,实现EJB3.0的厂商可以大大地优化整体性能和提升开发者的体验。例如,在JBoss EJB3.0的实现中,当你在用EntityManager持久化一个Entity Bean时,后台的Hibernate会话事务已经自动地帮定到调用方法的JTA 的事务上,在JTA 事务提交的同时Hibernate会话事务也提交了。你甚至可以使用一个简单的 @PersistenceContext 注释(稍候例子演示)将EntityManager和它后台的Hibernate事务绑定到一个stateful session bean的应用事务中。在一个会话中应用事务横跨多个线程,这在事务性网页应用很有用,例如,多页面的购物车。
由于高度整合的EJB3.0的框架,使简单、集成的编程接口成为可能。Oracle EJB3.0框架和其后台的Toplink持久化服务也同样程度地整合。

另一个EJB3.0整合服务的绝好例子就是集群支持。假如你在一个服务器集群上部署了一个EJB3.0的应用,所有容错(fail-over)、负载均衡、分布式缓冲和状态复制都已经自动为应用所获得可用。后台的集群支持被隐藏在EJB3.0的框架后面,对EJB3.0开发者来说这些都是完全透明不可见的。

在Spring里,很难优化框架和服务之间的通讯。例如,为了使用Spring里的声明事务服务来管理Hibernate事务,你必须显示地在XML文件中配置Spring TransactionManager和Hibernate SessionFactory对象。Spring必须电显示地管理横跨多个HTTP请求的事务。除此之外,没有别的方法均衡Spring应用里的集群。

服务组合的弹性
由于Spring的服务整合代码作为编程接口的一部份暴露在外,应用开发者有按自己需求装配服务的弹性。这个特点使你能够组合自己的轻量级应用服务器。Spring的一个普遍用法就是将Tomcat和Hibernate组合在一起支持数据库驱动的web应用。在这种情况,Spring本身提供事务服务,Hibernat提供持久化服务——这种设置创建了一个袖珍型的应用服务器。

EJB3.0应用服务器典型地不提供这种根据需求任你挑捡服务的弹性空间。大多数时间,你得到的只是一系列包装好的特性,其中一些你可能根本就不需要。但是如果应用服务器像JBoss一样提供一个模块性的内部设计,那么你可以只取其中一部分,而把不必要的部分剥去。在任何情况,去自定义一个功能强大的应用服务器是没有什么价值的。

当然,假如应用已经超过单个点,那么你应该加入常用服务器上的服务,例如,资源池(resource pooling),消息队列(message queuing)和集群(clustering)。就总体的资源消耗而言,Spring解决方法和其他EJB3.0解决方法一样是重量级的。

在Spring框架里,具有弹性的服务装配使得将虚拟对象而不是真正的业务对象绑定到应用中做脱离容器的单元测试更简单。在EJB3.0应用中,大多数组件都是简单POJO,他们可以很容易地在容器外被测试。但是对于与容器服务相关的对象(例如持久化实实体管理器EntityManager)建议用容器内测试。因为这样会比虚拟对象测试方法更简单,强壮及准确。

XML Vs.注解
从应用开发者的观点上来看,Spring的编程开发接口主要基于XML配置文件而EJB3.0广泛地应用Java注解。XML可以表达复杂的关系,但是它也冗长且不够健壮;注解简单明了,但是很难在注解里表达复杂或继承性的关系。

Spring选择XML或EJB3.0选择注解都是有他们两者框架后的体系结构决定的。因为注解只能容纳很少的配置信息,只有整合前的框架(重头戏都在框架里)才可以把广泛地使用注解作为配置选择。正如我们所讨论过的,EJB3.0刚好符合这个要求,而Spring作为一个普通的DI框架并不符合。 

当然,EJB3.0和Spring都相互取长补短,在某种程度上他们都支持XML和注解。例如,在EJB3.0中,XML配置文件作为一个可选的重载机制来改变注解的默认行为。注解也可以配置一些Spring服务。

通过例子是学习XML和注解方式之间差异的最好方法。在下面几个环节里,让我们来看看Spring和EJB3.0是怎样提供关键服务给应用的。

声明性服务
Spring和EJB3.0都将运行时服务(例如,事务、安全、日志和配置服务)绑定到应用。因为这些服务于应用的业务逻辑是没有直接联系,他们只是由应用本身管理。换句话说,这些服务在运行时由容器透明地应用到应用中。开发者或是管理者配置容器,准确地告诉它什么时候怎样应用这些服务。

EJB3.0运用Java注解来配置声明性服务,而Sring使用XML配置文件。在大多数情况下,EJB3.0注解方式对于这种服务更简单明了。这里有一个在EJB3.0中将事务服务运用到POJO的例子。

public class Foo {
    
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public bar () {
      // do something ...
    }    
}



你也可以为一个代码段声明多个属性,应用多个服务。这是一个在EJB3.0里同时应用事务和安全服务到POJO的例子。

@SecurityDomain("other")
public class Foo {
    
    @RolesAllowed({"managers"})
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public bar () {
      // do something ...
    }  
}



使用XML说明代码属性和配置声明性服务会导致冗长和不稳定的配置文件。下面是一个在Spring应用中的XML片段,其应用一个非常简单的Hibernate事务到方法Foo.bar()中。

<!-- Setup the transaction interceptor -->
<bean id="foo"
  class="org.springframework.transaction
        .interceptor.TransactionProxyFactoryBean">
    
    <property name="target">
        <bean class="Foo"/>
    </property>
    
    <property name="transactionManager">
        <ref bean="transactionManager"/>
    </property>
    
    <property name="transactionAttributeSource">
        <ref bean="attributeSource"/>
    </property>
</bean>

<!-- Setup the transaction manager for Hibernate -->
<bean id="transactionManager"
  class="org.springframework.orm
         .hibernate.HibernateTransactionManager">
    
    <property name="sessionFactory">
        <!-- you need to setup the sessionFactory bean in
             yet another XML element -- omitted here -->
        <ref bean="sessionFactory"/>
    </property>
</bean>

<!-- Specify which methods to apply transaction -->
<bean id="transactionAttributeSource"
  class="org.springframework.transaction
         .interceptor.NameMatchTransactionAttributeSource">
  
    <property name="properties">
        <props>
            <prop key="bar">
        </props>
    </property>
</bean>



XML的复杂度会以几何级数增长,如果你向同一个POJO添加更多的拦截器(interceptors)(例如安全拦截器)。意识到只有XML配置文件的局限,Spring使用Apache Commons 元数据在Java源码中来说明事务属性。最新版本的Spring1.2也支持JDK-1.5风格注解。要使用事务元数据,你须要将上面的transactionAttributeSourc bean变成一个AttributesTransactionAttributeSource实例。并为元数据拦截器添加额外邦定。

    class="org.springframework.aop.framework.autoproxy
           .DefaultAdvisorAutoProxyCreator"/>
<bean id="transactionAttributeSource"
    class="org.springframework.transaction.interceptor
           .AttributesTransactionAttributeSource"
    autowire="constructor"/>
<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor
           .TransactionInterceptor"
    autowire="byType"/>
<bean id="transactionAdvisor"
    class="org.springframework.transaction.interceptor
           .TransactionAttributeSourceAdvisor"
    autowire="constructor"/>
<bean id="attributes"
    class="org.springframework.metadata.commons
           .CommonsAttributes"/>



当你有很多事务性方法时,Spring元数据可以简化transactionAttributeSource。但是这并没有解决XML配置文件的根本问题。冗长而又繁琐的事务拦截器, transactionManager,和transactionAttributeSource仍然需要。

依赖注入(Dependency Injection, DI)

中间件容器的一个关键好处之一就是它可以让开发者建造一个关系耦合松散的应用。服务端客户只需要知道服务的接口。容器依据具体的实现实例化服务对象,使他们为客户端所用。在不改变接口和客户端代码的情况下,这使得容器可以在多种服务实现之间切换。

依赖注入的模式是实现耦合松散应用的最好方法之一。它更易用,比其他方法也明了多了,比如通过JNDI依赖性查询或容器回调。使用DI,框架就像一个对象工厂,它创建服务对象然后按照运行时配置将这些服务对象注入到应用的POJO里。站在应用开发者的角度,客户端POJO在被使用时可自动获得正确的服务对象。

Spring和EJB3.0都提供广泛的DI模式支持。但是他们之间仍存在很大的不同之处。Spring支持一般意义上且复杂的DI API,其基于XML配置文件。EJB3.0支持大多数普通服务对象(如EJB及context对象)的注入和任何简单注解的JDNI。 

EJB63.0注解非常简单易用。@Resource 标记表示注入大多数普通服务对象和JDNI对象。以下例子展示了怎样把服务的JDNI的默认DataSource 对象注入到POJO的一个属性变量中。DefaultDS是DataSource.的JDNI名字。MyDb变量在第一次被使用时被赋上了正确的值。

public class FooDao {

    @Resource (name="DefaultDS")
    DataSource myDb;
    
    // Use myDb to get JDBC connection to the database
}



除了直接属性变量注入,Ejb3.0的@Resource注解也可以用来在setter方法里面注入对象。例如,下面的例子就是注入session context对象。应用从不会显示地调用setter方法,其在其他方法被调用前由容器来触发。

@Resource 
public void setSessionContext (SessionContext ctx) {
    sessionCtx = ctx;
}



针对更复杂的服务对象,定义了专用的注入注解。例如,@EJB注释用来注入EJB的Stub,@PersistenceContext注解用来注入处理EJB3.0实体bean访问数据库的EntityManager对象。下面是一个怎样将EntityManager对象注入有状态的 session bean的例子。@PersistenceContext的type属性具体说明了被注入的EntityManager有一个扩展的事务transaction context。transaction context并不会同JTA transaction manager一起自动提交。因此它可以用在在一个会话横跨多个线程的应用事务中。

@Stateful
public class FooBean implements Foo, Serializable {

    @PersistenceContext(
      type=PersistenceContextType.EXTENDED
    )
    protected EntityManager em;
    
    public Foo getFoo (Integer id) {
        return (Foo) em.find(Foo.class, id);
    }
}



EJB3.0标准通过注解可以被注入的服务器资源。但是它并支持将用户定义的应用POJO之间的相互注入。

在Spring中,首先你必须为POJO中的服务对象定义一个setter方法。下面的例子说明POJO需要一个Hibernate session 的引用

public class FooDao {
    
    HibernateTemplate hibernateTemplate;
    
    public void setHibernateTemplate (HibernateTemplate ht) {
        hibernateTemplate = ht;
    }
    
    // Use hibernateTemplate to access data via Hibernate
    public Foo getFoo (Integer id) {
        return (Foo) hibernateTemplate.load (Foo.class, id);
    }
}



然后,以XML里的元素作为桥梁具体描述容器怎样在运行时得到服务对象并将其注入到POJO里。以下是一个XML例子,具体描述了将一个数据源绑定到一个Hibernate session factory,然后从Hibernate session factory到Hibernate template object,最后从template object到应用的POJO。Spring代码如此复杂的部分原因是因为我们须手手动注入后台Hibernate plumbing objects。而EJB3.0 EntityManager是自动被服务器管理和配置。这又将我们带回到Spring并不像EJB3.0那样高度与服务整合的论点上。

<bean id="dataSource" 
  class="org.springframework
         .jndi.JndiObjectFactoryBean">
    <property name="jndiname">
        <value>java:comp/env/jdbc/MyDataSource</value>
    </property>
</bean>

<bean id="sessionFactory"
  class="org.springframework.orm
         .hibernate.LocalSessionFactoryBean">
    <property name="dataSource">
        <ref bean="dataSource"/>
    </property>
</bean>

<bean id="hibernateTemplate"
  class="org.springframework.orm
         .hibernate.HibernateTemplate">
    <property name="sessionFactory">
        <ref bean="sessionFactory"/>
    </property>    
</bean>

<bean id="fooDao" class="FooDao">
    <property name="hibernateTemplate">
        <ref bean="hibernateTemplate"/>
    </property>
</bean>

<!-- The hibernateTemplate can be injected
        into more DAO objects -->



虽然,Spring里基于XML的依赖注入语法复杂,但却功能强大。你可以将任何POJO注入到另一个POJO,包括你自己在应用定义的那些POJO。假如你想在EJB3.0应用中用Spring的DI功能 ,你可以通过JNDI把一个Spring bean factory注入到EJB。在一些EJB3.0的应用服务器里,厂商可能会额外定义非标准的POJO注入API。一个很好的例子就是JBoss MicroContainer。它比Spring更一般化,因为它处理Aspect-Oriented Programming(AOP)的依赖。

结论

Spring和Ejb3.0虽然都是为了向企业服务提供耦合松散的POJO,但是使用了不同方法来达到这个目的。两者都大量地使用了依赖注入。

对于EJB3.0,基于标准的方案、注解的广泛使用、与应用服务器的高度整合都使得EJB3.0拥有更好的厂商无关性,更高的开发效率。依赖注入和集中的XML配置文件协调一致的使用使开发者能够构建更有弹性的应用,并且可以同时和几个应用服务提供者一起协作。

鸣谢
作者感谢tephen Chambers,、ill Burke、Andy Oliver的珍贵意见。

资源
Spring框架(参见CodeZoo: Spring) 
EJB 3.0 
JBoss EJB 3.0 
Oracle Application Server EJB 3.0 Preview 

Michael Juntao Yuan 善长于提供end-to-end的企业解决方案, 也是一个移动方面的专家,是avid这个开源项目的支持者。

POJO应用框架:Spring与EJB3.0的比较