EJB3.0和Spring比较
(译“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隔绝了开发者和应用服务的具体实现。比如说:JBoss的EJB3.0是在Hibernate的基础商实现的,而Oracle的EJB3.0是基于TopLink实现的。但开发者在使用EJB3.0开发企业应用时,即不需要学习Hibernate也不需要学习TopLink,EJB3.0依然能够在JBoss或Oracle上面运行得很好。厂商无关性时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框架使用不同的DAO和Helper类以利用JDBC、Hibernate、iBatis或JDO。所以,如果你打算改变数据持久化方式,比如从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.0、Hibernate、JBoss中的Tomcate的紧密集成。在Oracle EJB3.0框架中的情况是一致的,只不过Hibernate换成了TopLink而已。
EJB3.0服务集成的另一个例子是集群支持。如果你将一个EJB3.0应用部署到一个服务集群中,所有的负载均衡、分布缓存、状态复制等全都自动的生效了。底层的集群服务隐藏在EJB3.0编程接口后面,屏蔽了所有的复杂性。
在Spring这边,优化框架和服务之间的交互要困难得多。比如,如果要使用Spring的声明式事务服务来管理Hibernate事务,必须手工显式的在XML配置文件中配置Spring的TransactionManager和Hibernate的SessionFactory对象。Spring应用的开发者必须显式的管理那些跨越多个HTTP请求的事务。另外,Spring没有简单的利用集群的方法。
3. 服务装配灵活性 (Flexibility in Service Assembly)
因为Spring中的服务集成代码是以编程接口的形式发布的,所以应用开发者在装配服务时可以获得极大的灵活性。这使得开发者可以装备自己的轻量级的应用服务。Spring 的一个最普遍的应用就时粘合Tomcat和Hibernate来支撑简单的数据库驱动应用。在这种情况下,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文件可以表达复杂的(从属)关系,但是也非常冗长而且不够健壮。注释则比较简单,但却很难表达复杂关系和子属结构。
Spring和EJB3.0分别对XML配置文件和Java注释的选择是由两种框架背后的构架设计所决定的。因为注释只能容纳非常少量的配置信息,所以只有预集成的框架(大多少工作已经在框架内部完成)可以大量使用注释作为其配置解决方案。正如同我们已经讨论的那样,EJB3.0符合这种要求,而Spring,作为一种DI框架,就不能这么做。
当然,EJB3.0和Spring都在发展中都相互借鉴了对方的长处,都在不同的程度上支持XML配置或Java注释配置。XML配置文件在EJB3.0中也被用来配置注释的缺省行为。而在Spring中,注释也用来配置Spring服务。
5. 声明式服务(Declarative Services)
Spring和EJB3.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配置文件的基本问题,即:冗长。并且拦截点transactionManager和transactionAttributeSource的配置依然不能省略。
6. 依赖注入 (Dependency Injection)
使用中间件容器的一个关键优点是可以使得开发者实现松散耦合的应用。服务客户端只需要知道服务接口而不是实现。容器承载具体的服务对象实现供客户端访问。这允许容器在不同的服务实现之间切换,而接口和客户端代码不需要任何的修改和变动。
依赖注入模式(以下简称DI)是在应用中实现松散耦合的的最佳实践。相对于传统的JNDI lookup方式、容器回调方式,DI模式更优雅、更容易使用。使用DI,框架完全起着一个对象工厂的作用,在应用中创建服务对象、依照运行时配置给POJO注入服务对象。在应用开发者看来,当需要时,客户端POJO能够自动的获得正确的服务对象。
Spring和EJB3.0都支持DI模式,但他们有着深刻的不同。Spring支持通用的(但复杂的)基于XML配置文件的依赖注入API。EJB3.0支持注入大多数服务对象(如EJB和上下文对象)和通过简单注释声明的JNDI对象。
EJB3.0的DI注释异常的简洁和易用。@Resource标签注入大多数普通服务对象和JNDI对象。以下的例子显示如何从JNDI注入服务的缺省DataSource对象到一个POJO中去,DefaultDS是JNDI中DataSource的一个名字。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);
}
}
然后,你就可以定义容器如何去取得服务对象,并在运行时POJO与XML元素关联起来。下例显示通过XML元素定义将数据源与Hibernate session factory联系起来,session factory和一个Hibernate临时对象联系起来,并最终和应用POJO对象联系起来。Spring代码的复杂性有部分来自于必须人工处理这些注入的底层,这个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,包括自定义的。如果你真的很希望在EJB3.0的应用中使用Spring的DI能力,你可以通过JNDI向EJB中注入Spring bean factory。在某些EJB3.0应用服务中,厂商可能会定义额外的非标准的API来注入POJO。一个很好的例子是JBoss MicroContainer,甚至比Spring更普遍的支持AOP。
7. 原文结论
尽管Spring和EJB3.0都瞄准提供低藕合POJO的企业服务,但使用了非常不同的方法来实现。DI模式在两种框架中都得到大量的使用。
EJB3.0是基于标准化的实现,大量使用注释,和应用服务紧密集成。带来的结果是厂商独立和开发者生产力的提高。Spring,使用依赖注入,并且围绕XML配置文件,给开发者带来很大的灵活性,并可以同时在不同的应用服务上运行。
附录:
原文:POJO Application Frameworks: Spring Vs. EJB 3.0