EJB3和Spring技术体系比较

http://www.infoq.com/cn/articles/ejb3-spring-compare

随着EJB3规范以及支持EJB3的Java EE应用服务器的即将发布,全新Java EE体系架构的新战争将拉开帷幕,在过去3年中如火如荼的Spring占据了Java EE应用开发基础平台的大半江山,面对EJB3和Spring你应该如何选择呢?

作为一个架构师,我对EJB是既爱且恨,对Spring又恨又爱,现在我们来也把这两大技术体系来做一个全面分析和对比,希望能给大家在进行技术选型时一个更好的参考。

1. 法制 VS “民主”

EJB规范一直由国际组织JCP来制定,一经通过,即作为官方标准,且各厂商都会不遗余力的推动,所以对于企业应用来说,EJB就是法,以EJB为企业应用的基础架构暂且称为法治;Spring来自开源社区,由众多的开源软件开发者参与,逐步形成的一种流行的体系标准,它的设计以IoC(反转控制)为核心,提倡所谓的“零”侵入设计原则,这里暂且称之为民主。

支持EJB的应用服务器一般是一个大而全的产品,包括了构建企业应用需要的方方面面,如果需要额外扩展一般不容易,如果对一个应用服务器不满意的话,那么可以且也只能更换整个应用服务器了,好在由于应用服务器市场百花齐放,从免费到低端再到高端,您可以任意选择;Spring从IoC容器发展而来,通过不断集成AOP、MVC、OR/Mapping以及几乎您能想到的各项服务而提供完善的企业应用架。对于一个应用,你可以自由选择具体的技术框架的实现,SSH就是最常用一套组合,然而且不说是否每个架构师拥有正确选择的能力,无论如何,最终的选择在设计之初一旦确定,要想更换便不那么容易,你不可能轻松的将一个基于Spring + Struts的应用轻松的移植到Spring + WebWork,更不能轻松的将一个基于Spring + Hibernate的应用轻松的移植到Spring + iBatis,所以对于需要长期维护和发展的应用来说,将只能寄希望于你采用的框架都能够很好的发展,并且能在升级的同时保证向前的兼容性。

综上所述,EJB由于对于整个世界是标准的,就好像是一部国际法,一旦遵循,全球通用,你可以比较轻松的在WebSphere、WebLogic甚至 JBoss之间进行切换,所以如果选择EJB,你将在一个”法制”的环境下获得最大的民主;而Spring对于整个世界看似民主的,然而一旦整套架构确定下来,却成了专制,犹如美国式的民主,一旦被它征服,就成为它的专政统治了,想挣脱它的控制可就不那么容易了,其中的利害,大家细细品味吧。

2. 轻量级组件 VS 轻量级内核 VS 轻量级容器

关于轻量级内核,不论属实是否,现今的应用服务器都宣称采用了微内核技术,在此基础上建立Java EE的各项服务构建成完善的应用服务器;而Spring本身就是一个基于IoC的轻量内核,然后通过集成第三方的服务器来提供完整的架构。

EJB组件曾经被认为是一个重量级的组件,而备受批评,EJB3规范的重要目标就是简化EJB的开发,提供一个容器管理的轻量级的组件方案。

但是有必要提醒一下,轻量级的组件,并不意味着提供服务的容器是轻量的,不管是EJB2还是EJB3,应用服务器因为需要管理组件的负责生命周期以及行为,并且内置提供了各项服务,容器自然是一个重量级的服务;至少现在看来,现有的Application Server提供的容器都还不足够的轻量,从个人偏好来说,我就非常喜欢JBoss 2.4这个版本,它有我需要的功能,同时又够简单,而现在,JBoss 4的启动速度已经逐渐让我对它对失去了耐心。

而对于Spring,也有同样的问题,轻量级的内核,也不意味着整个框架是轻量的,更不意味着基于Spring的整个应用架构是轻量的。对于 Spring,你需要去寻找并粘合各种服务,然后让他们能够稳定的在一起工作,如果应用对技术的需求较多,伸缩性要求也较高,你就会不断的在应用服务中加入其他服务,如:资源池、消息队列、集群等。当加入这些后,Spring的解决方案已经和Java EE Application Server解决方案一样重量级了。

追求简单、轻量,是每一个应用架构的目标,对于企业应用的构建来说,轻量级组件标准+轻量的内核+轻量级的容器,并以此构建轻量级的应用平台,才是最终需要的。如果有轻量级的容器出现,将帮助EJB3在企业应用中重新占据有利的地位。

3. 可管理性与可控性

这个问题对于一次性交付的项目也许不是问题,但是对于质量要求更高、生命周期更长的产品,却是衡量平台和架构的重要因素。

基于Spring架构的应用,由于过分的自由和灵活,随着项目的进展,逐渐集成的第三方框架越来越多,很难保证集成的服务和编写的组件中有没有漏洞,甚至相互之间有严重的冲突,那么,掌控整个项目的质量成了难题,光是一页接一页的配置文件,就知道今后的维护成本也就随之增高,回想一下EJB2.0时代的ejb-jar.xml吧;而EJB因为集成的都是标准服务,而且组件模型也是固定的,加之应用服务器一般提供控制台,用来查看运行时的各项属性,并可对服务进行实时的管理,显然比Spring开发的应用可控性更好。

4. 功能性对比

4.1 IoC容器,AOP能力

在IoC的能力Spring要略强一些,但是在EJB3中可以完全用Annotation方式进行注入,在开发上要简单很多,对于一些相对比较固定的注入,采用Annotation更好,而对于一些可能需要经常变动的注入,XML更加灵活,EJB3刚好提供了这样的两种解决方案。如果你已经患有XML恐惧症,那么EJB3无疑将给您以解脱。

同时,EJB3组件中,支持多种方式注入,比如依赖于名称、接口或者JNDI名,另外还支持使用@PersistenceContext注入 EntityManager,@Resource注入服务器资源,如EJBContext、TimerService等,而一些Annotation已经成为JDK6的一部分,将来可能直接被JDK支持。

AOP方面,如果您需要彻底的AOP,并且在Spring中集成了AspectJ,那么EJB3自然无法比拟,但是如果您的项目以够用为原则,只需要一般方法拦截意义上的AOP,EJB3提供的各种回调方法应该可以满足您的要求了。

4.2 事务处理

EJB的看家本领,Spring也通过提供TransactionTemplate以及集成第三方事务处理器来支持JTA,都支持申明式事务,可以BMT,CMT,但无论如何,移植的器官总也没有自身长的好吧。

4.3 分布式能力

一般使用Java EE体系的公司都认为这是EJB的最大长处,但是实施并不如想象那样,一来绝大多数都是Web应用,依赖Web提供的分布式能力已经可以满足90%的需要了,二来大家基本上都是Web容器和EJB容器整体部署,EJB组件的分布部署少之又少。当然如果您需要Web层和应用层分开部署,那么Spring一定不在你的考虑范围之内了。

4.4 Cluster能力

Cluster也是EJB的传统优势,但是老师说,能够发挥EJB集群优势的地方并不多,因为即使项目中采用了EJB,一般也采用Stateless SessionBean,而使用HttpSession Cluster,既然如此,无论EJB还是Spring,大家都是平等的。当然,如果您正在构建一个大型的应用,对集群的能力要求非常高,比如需要事务级的Cluster,而且还有分布式的需求,那么估计没有多少因素会让您考虑Web Server + Spring的架构了。

4.5 Web Services

EJB3中的Web Service和EJB组件集成得如此之好,使用起来再简单不过了,如下面实例所示,JAX-WS也将逐步成为Java Web Service事实标准;至于Spring可以实现各种基于Http的远程调用方法,其优势并不明显。

@Stateless
@Remote
@Local
@WebService(endpointInterface = "jfox.test.ejb3.webservice.Calculator")
public class CalculatorBean implements CalculatorRemote, CalculatorLocal {

    public int add(int x, int y) {
        return x + y;
    }

    public int subtract(int x, int y) {
        return x - y;
    }

}

4.6 集成第三方框架

如果需要集成第三方框架的时候,估计您需要Spring了,当然前提是Spring已经给出很好的集成方案;而如果采用EJB,则需要视特定的应用服务器了,推荐当类库来用,或者使用context listener来启动,是在不行,只能基于特定的应用服务器来进行集成,一般来说,应用服务器均提供了JMX集成能力。

5. 总结

纵观人类历史,官方过于强势,则必然官逼民反;而民间力量过于强大,社会必将不稳定,这都是我们不愿看到的,在技术世界里也一样。对于EJB3 和Spring这两种方案,Spring现在处于压倒性的优势一方,希望EJB3的出现,一来能为官方挽回一些失去的领地,二来也能继续引发更多的探讨,不再拘束于一家之言,只有百家争鸣的环境,才能让开发人员和架构人员对企业应用的构建认识得更加完善,所以最好的方式是EJB3和Spring互相促进,和谐发展。

期待一个轻量的真正以开发需求为中心的EJB3应用服务器的出现,为疲软的EJB市场注入新的活力!



EJB3.0Spring比较

(译“POJO Application Frameworks: Spring Vs. EJB 3.0”)

摘要:

阅读“POJO Application Frameworks: Spring Vs. EJB 3.0”一文的读书笔记,翻译其中部分,主要分6点讲述两者的差别。

 

0.比较目录

1) Vendor Independence

2) Service Integration

3) Flexibility in Service Assembly

4) XML versus Annotation

5) Declarative Services

6) Dependency Injection

 

1. 厂商无关性(Vendor Independence )

Java平台最具竞争力的优势是厂商无关,(不依赖某个特别的厂商)。EJB3.0的标准也是开放的,并且继承了这一优势。EJB3.0的规范是由包括几乎所有Java社区内的开源和商业组织定义并支持的。EJB3.0隔绝了开发者和应用服务的具体实现。比如说:JBossEJB3.0是在Hibernate的基础商实现的,而OracleEJB3.0是基于TopLink实现的。但开发者在使用EJB3.0开发企业应用时,即不需要学习Hibernate也不需要学习TopLinkEJB3.0依然能够在JBossOracle上面运行得很好。厂商无关性时EJB3.0框架现在区别于任何其他POJO中间件框架的一大特色。

当然,也有一些EJB3.0的批评者立刻指出,EJB3.0的规范还没有最终定稿,EJB3.0得到业界主要J2EE厂商完全接受还有一到两年的路要走。是的,是这样,但是既便现在企业软件采用的应用服务还不支持EJB3.0,但是,(在将来)仍然能够通过下载安装“可嵌入(embeddable)”EJB3.0产品来获得支持。这方面走在前面的JBoss就已经推出的可嵌入EJB3.0产品,开源并且支持所有兼容J2SE-5.0的环境,现在正处于beta测试阶段。其他厂商也正在迅速跟进,很快会推出他们自己的可嵌入的EJB3.0产品,特别是EJB3.0规范中的“数据持久化”部分。

另一方面,Spring是一个非基于标准的技术解决方案,并且在将来很长一段时间也将继续维持这样的状况。你可以在任何应用服务上使用Spring框架,但同时意味着你的应用已经和Spring以及Spring所选用的应用服务套牢了。

  • Spring框架是一个开源项目(而不是标准),自己定义了一套XML配置文件大纲以及程序接口。几乎所以的开源项目都是这样。从长远考虑,使用Spring框架的企业应用恐怕得更关注Spring项目的运作情况(以及Interface21 Inc. ――大部分Spring的核心开发人员都来自于Interface21 Inc.)。另外,如果企业应用中采用了Spring专用服务(Spring-specific services),比如Spring事务管理或Spring MVC,事实上这个企业应用已经和Spring牢牢绑死了。
  • Spring没有实现和最终服务提供者的隔离。比如,现在在选择数据持久化服务时,Spring框架使用不同的DAOHelper类以利用JDBCHibernateiBatisJDO。所以,如果你打算改变数据持久化方式,比如从JDBC改为Hibernate,你就需要重构你的应用代码了,新的Helper类编写也是不可避免的。

2.服务集成 (Service Integration)

Spring框架站在一个高于应用服务和服务相关类库的层次上。服务集成代码(如:数据访问和Helper类等)位于框架中,并直接暴露给应用开发者。EJB3.0则相反,EJB3.0框架牢牢的集成到应用服务中,所有的服务集成代码都被封装到标准接口下面。

这样的设计使得EJB3.0的厂商可以集中优化总体性能,优化开发者体验。例如:在JBoss EJB3.0实现中,当你使用EntityManager持久化一个实体Bean POJO时,底层的Hibernate会话事务被自动的指派调用JTA事务的方法,并在JTA事务提交时被提交。使用一个简单的注释(annotation@PersistenceContext,就可以将EntityManager和底层的Hibernate事务绑定到一个有状态的会话Bean中应用事务中。应用事务在一个会话中跨越多线程,这在Web应用的事务处理中非常有用,比如一个包含多页面的购物车功能。这样的简单性和接口集成都得益于EJB3.0HibernateJBoss中的Tomcate的紧密集成。在Oracle EJB3.0框架中的情况是一致的,只不过Hibernate换成了TopLink而已。

EJB3.0服务集成的另一个例子是集群支持。如果你将一个EJB3.0应用部署到一个服务集群中,所有的负载均衡、分布缓存、状态复制等全都自动的生效了。底层的集群服务隐藏在EJB3.0编程接口后面,屏蔽了所有的复杂性。

Spring这边,优化框架和服务之间的交互要困难得多。比如,如果要使用Spring的声明式事务服务来管理Hibernate事务,必须手工显式的在XML配置文件中配置SpringTransactionManagerHibernateSessionFactory对象。Spring应用的开发者必须显式的管理那些跨越多个HTTP请求的事务。另外,Spring没有简单的利用集群的方法。

3. 服务装配灵活性 (Flexibility in Service Assembly)

因为Spring中的服务集成代码是以编程接口的形式发布的,所以应用开发者在装配服务时可以获得极大的灵活性。这使得开发者可以装备自己的轻量级的应用服务。Spring 的一个最普遍的应用就时粘合TomcatHibernate来支撑简单的数据库驱动应用。在这种情况下,Spring提供事务服务,Hibernate提供持久化服务,这样的配置构成了一个迷你型的应用。

EJB3.0应用服务没有给你这类可以选择的灵活性。在大多数情况下,你将得到一套已经打包好得特性,尽管其中有些你根本就用不着。当然,如果应用服务内部是模块化设计,比如JBoss,你可以剥离这些你不用得部分。这样得定制对一个成熟得应用服务是非常有价值的。

当然,如果应用发生伸缩,范围超出了单一服务节点,你就应该在应用服务中加入其他服务,如:资源池、消息队列、集群等。当加入这些后,Spring的解决方案已经和EJB3.0 一样重量级了。

使用Spring,服务装配的灵活性使得利用模拟对象(mock object)非常容易。模拟对象可以在单元测试中代替真实的服务对象,从而使得容器外测试得以成为可能。EJB3.0应用中,大部分组件都是简单的POJO,也很容易在容器外进行测试。但是,那些牵涉到容器对象的测试(如:EntityManager),容器内测试才是最佳选择,比使用模拟对象测试更简单、更健壮、更准确。

4. XML v.s 属性注释(XML Versus Annotation)

再应用开发者看来,Spring的编程接口主要是基于XML配置文件的基础上,而EJB3.0则大量使用Java注释。XML文件可以表达复杂的(从属)关系,但是也非常冗长而且不够健壮。注释则比较简单,但却很难表达复杂关系和子属结构。

SpringEJB3.0分别对XML配置文件和Java注释的选择是由两种框架背后的构架设计所决定的。因为注释只能容纳非常少量的配置信息,所以只有预集成的框架(大多少工作已经在框架内部完成)可以大量使用注释作为其配置解决方案。正如同我们已经讨论的那样,EJB3.0符合这种要求,而Spring,作为一种DI框架,就不能这么做。

当然,EJB3.0Spring都在发展中都相互借鉴了对方的长处,都在不同的程度上支持XML配置或Java注释配置。XML配置文件在EJB3.0中也被用来配置注释的缺省行为。而在Spring中,注释也用来配置Spring服务。

5. 声明式服务(Declarative Services)

SpringEJB3.0都为企业应用提供运行时服务,(如:事务、安全、日志消息、配置服务)。由于这些服务都不是直接与应用的业务逻辑相关联,所以都不是由应用来自行管理。事实上,这些服务由服务容器来进行运行时管理。开发者(或系统管理员)配置容器来决定何时以及如何申请这些服务。

EJB3.0使用Java注释来配置声明式服务,Spring使用XML配置文件。在大多数情况下,EJB3.0的注释声明显得更为简单和优雅。以下是一个例子,EJB3.0的一个POJO方法中通过注释申请事务支持。

 

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

 

可以对同一代码段定义多个属性,从而申请多个运行时服务,例子如下:

 

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

 

使用XML来定义属性并配置声明式服务的结果将式一个冗长而不稳定的配置文件。以下即是一个例子,通过一个XML元素为一个Spring应用中Foo.bar()方法声明Hibernate事务支持。

 

<!-- 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>

 

如果对同一POJO加入更多的拦截点,XML文件的复杂性将成几何倍数增加。意识到只使用XML来进行配置的局限性,Spring也支持使用Apache Commons元数据在Java源码中定义事务属性。要使用事务元数据,你需要改变transactionAttributeSource的实现为AttributeTransactionAttributeSource,并且添加新的元数据拦截点。

 

<bean id="autoproxy"
    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配置文件的基本问题,即:冗长。并且拦截点transactionManagertransactionAttributeSource的配置依然不能省略。

6. 依赖注入 (Dependency Injection)

使用中间件容器的一个关键优点是可以使得开发者实现松散耦合的应用。服务客户端只需要知道服务接口而不是实现。容器承载具体的服务对象实现供客户端访问。这允许容器在不同的服务实现之间切换,而接口和客户端代码不需要任何的修改和变动。

依赖注入模式(以下简称DI)是在应用中实现松散耦合的的最佳实践。相对于传统的JNDI lookup方式、容器回调方式,DI模式更优雅、更容易使用。使用DI,框架完全起着一个对象工厂的作用,在应用中创建服务对象、依照运行时配置给POJO注入服务对象。在应用开发者看来,当需要时,客户端POJO能够自动的获得正确的服务对象。

SpringEJB3.0都支持DI模式,但他们有着深刻的不同。Spring支持通用的(但复杂的)基于XML配置文件的依赖注入APIEJB3.0支持注入大多数服务对象(如EJB和上下文对象)和通过简单注释声明的JNDI对象。

EJB3.0DI注释异常的简洁和易用。@Resource标签注入大多数普通服务对象和JNDI对象。以下的例子显示如何从JNDI注入服务的缺省DataSource对象到一个POJO中去,DefaultDSJNDIDataSource的一个名字。myDb变量是在第一次使用前一个自动分配得修正变量。

 

public class FooDao {

 

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

 

作为补充,EJB3.0@Resource注释属性也可以通过setter方法来设置。下例注入一个会话上下文。应用程序无需显式的去调用setter方法,而只是由容器在其他方法被调用之前引入。

 

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

 

对于更复杂的服务对象,某些特殊的注入注释并不可用。如:@EJB用于注入EJB Stub,而@PersistenceContext用于注入EntityManager对象,以便为EJB3.0实体bean处理数据访问。下例显示如何向一个有状态的session bean注入一个EntityManager。@PersistenceContext注释的type属性定义了注入的EntityManager对象拥有一个扩展的事务上下文。这代表事务不会由JTA事务管理器自动的提交,从而可以在一个会话中跨线程的使用。

 

@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 factory的情况。

 

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);
    }
}

 

然后,你就可以定义容器如何去取得服务对象,并在运行时POJOXML元素关联起来。下例显示通过XML元素定义将数据源与Hibernate session factory联系起来,session factory和一个Hibernate临时对象联系起来,并最终和应用POJO对象联系起来。Spring代码的复杂性有部分来自于必须人工处理这些注入的底层,这个EJB3.0EntityManager通过服务器自动的管理配置形成对比。但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,包括自定义的。如果你真的很希望在EJB3.0的应用中使用SpringDI能力,你可以通过JNDIEJB中注入Spring bean factory。在某些EJB3.0应用服务中,厂商可能会定义额外的非标准的API来注入POJO。一个很好的例子是JBoss MicroContainer,甚至比Spring更普遍的支持AOP

7. 原文结论

尽管SpringEJB3.0都瞄准提供低藕合POJO的企业服务,但使用了非常不同的方法来实现。DI模式在两种框架中都得到大量的使用。

EJB3.0是基于标准化的实现,大量使用注释,和应用服务紧密集成。带来的结果是厂商独立和开发者生产力的提高。Spring,使用依赖注入,并且围绕XML配置文件,给开发者带来很大的灵活性,并可以同时在不同的应用服务上运行。

附录:

原文:POJO Application Frameworks: Spring Vs. EJB 3.0



你可能感兴趣的:(spring,框架,Hibernate,应用服务器,ejb,企业应用)