EJB3.0和Spring比較EJB3.0和Spring比較
(譯“POJO Application Frameworks: Spring Vs. EJB 3.0”)
摘要:
閱讀“POJO Application Frameworks: Spring Vs. EJB 3.0”一文的讀書筆記,翻譯其中部分,主要分6點(diǎn)講述兩者的差別。
0.比較目錄
1) Vendor Independence
2) Service Integration
3) Flexibility in Service Assembly
4) XML versus Annotation
5) Declarative Services
6) Dependency Injection
1. 廠商無(wú)關(guān)性(VendorIndependence)
Java平臺(tái)最具競(jìng)爭(zhēng)力的優(yōu)勢(shì)是廠商無(wú)關(guān),(不依賴某個(gè)特別的廠商)。EJB3.0的標(biāo)準(zhǔn)也是開(kāi)放的,并且繼承了這一優(yōu)勢(shì)。EJB3.0的規(guī)范是由包括幾乎所有Java社區(qū)內(nèi)的開(kāi)源和商業(yè)組織定義并支持的。EJB3.0隔絕了開(kāi)發(fā)者和應(yīng)用服務(wù)的具體實(shí)現(xiàn)。比如說(shuō):JBoss的EJB3.0是在Hibernate的基礎(chǔ)商實(shí)現(xiàn)的,而Oracle的EJB3.0是基于TopLink實(shí)現(xiàn)的。但開(kāi)發(fā)者在使用EJB3.0開(kāi)發(fā)企業(yè)應(yīng)用時(shí),即不需要學(xué)習(xí)Hibernate也不需要學(xué)習(xí)TopLink,EJB3.0依然能夠在JBoss或Oracle上面運(yùn)行得很好。廠商無(wú)關(guān)性時(shí)EJB3.0框架現(xiàn)在區(qū)別于任何其他POJO中間件框架的一大特色。
當(dāng)然,也有一些EJB3.0的批評(píng)者立刻指出,EJB3.0的規(guī)范還沒(méi)有最終定稿,EJB3.0得到業(yè)界主要J2EE廠商完全接受還有一到兩年的路要走。是的,是這樣,但是既便現(xiàn)在企業(yè)軟件采用的應(yīng)用服務(wù)還不支持EJB3.0,但是,(在將來(lái))仍然能夠通過(guò)下載安裝“可嵌入(embeddable)”EJB3.0產(chǎn)品來(lái)獲得支持。這方面走在前面的JBoss就已經(jīng)推出的可嵌入EJB3.0產(chǎn)品,開(kāi)源并且支持所有兼容J2SE-5.0的環(huán)境,現(xiàn)在正處于beta測(cè)試階段。其他廠商也正在迅速跟進(jìn),很快會(huì)推出他們自己的可嵌入的EJB3.0產(chǎn)品,特別是EJB3.0規(guī)范中的“數(shù)據(jù)持久化”部分。
另一方面,Spring是一個(gè)非基于標(biāo)準(zhǔn)的技術(shù)解決方案,并且在將來(lái)很長(zhǎng)一段時(shí)間也將繼續(xù)維持這樣的狀況。你可以在任何應(yīng)用服務(wù)上使用Spring框架,但同時(shí)意味著你的應(yīng)用已經(jīng)和Spring以及Spring所選用的應(yīng)用服務(wù)套牢了。
Spring框架是一個(gè)開(kāi)源項(xiàng)目(而不是標(biāo)準(zhǔn)),自己定義了一套XML配置文件大綱以及程序接口。幾乎所以的開(kāi)源項(xiàng)目都是這樣。從長(zhǎng)遠(yuǎn)考慮,使用Spring框架的企業(yè)應(yīng)用恐怕得更關(guān)注Spring項(xiàng)目的運(yùn)作情況(以及Interface21 Inc. ――大部分Spring的核心開(kāi)發(fā)人員都來(lái)自于Interface21 Inc.)。另外,如果企業(yè)應(yīng)用中采用了Spring專用服務(wù)(Spring-specific services),比如Spring事務(wù)管理或Spring MVC,事實(shí)上這個(gè)企業(yè)應(yīng)用已經(jīng)和Spring牢牢綁死了。 Spring沒(méi)有實(shí)現(xiàn)和最終服務(wù)提供者的隔離。比如,現(xiàn)在在選擇數(shù)據(jù)持久化服務(wù)時(shí),Spring框架使用不同的DAO和Helper類以利用JDBC、Hibernate、iBatis或JDO。所以,如果你打算改變數(shù)據(jù)持久化方式,比如從JDBC改為Hibernate,你就需要重構(gòu)你的應(yīng)用代碼了,新的Helper類編寫也是不可避免的。
2.服務(wù)集成 (Service Integration)
Spring框架站在一個(gè)高于應(yīng)用服務(wù)和服務(wù)相關(guān)類庫(kù)的層次上。服務(wù)集成代碼(如:數(shù)據(jù)訪問(wèn)和Helper類等)位于框架中,并直接暴露給應(yīng)用開(kāi)發(fā)者。EJB3.0則相反,EJB3.0框架牢牢的集成到應(yīng)用服務(wù)中,所有的服務(wù)集成代碼都被封裝到標(biāo)準(zhǔn)接口下面。
這樣的設(shè)計(jì)使得EJB3.0的廠商可以集中優(yōu)化總體性能,優(yōu)化開(kāi)發(fā)者體驗(yàn)。例如:在JBoss EJB3.0實(shí)現(xiàn)中,當(dāng)你使用EntityManager持久化一個(gè)實(shí)體Bean POJO時(shí),底層的Hibernate會(huì)話事務(wù)被自動(dòng)的指派調(diào)用JTA事務(wù)的方法,并在JTA事務(wù)提交時(shí)被提交。使用一個(gè)簡(jiǎn)單的注釋(annotation)@PersistenceContext,就可以將EntityManager和底層的Hibernate事務(wù)綁定到一個(gè)有狀態(tài)的會(huì)話Bean中應(yīng)用事務(wù)中。應(yīng)用事務(wù)在一個(gè)會(huì)話中跨越多線程,這在Web應(yīng)用的事務(wù)處理中非常有用,比如一個(gè)包含多頁(yè)面的購(gòu)物車功能。這樣的簡(jiǎn)單性和接口集成都得益于EJB3.0、Hibernate、JBoss中的Tomcate的緊密集成。在Oracle EJB3.0框架中的情況是一致的,只不過(guò)Hibernate換成了TopLink而已。
EJB3.0服務(wù)集成的另一個(gè)例子是集群支持。如果你將一個(gè)EJB3.0應(yīng)用部署到一個(gè)服務(wù)集群中,所有的負(fù)載均衡、分布緩存、狀態(tài)復(fù)制等全都自動(dòng)的生效了。底層的集群服務(wù)隱藏在EJB3.0編程接口后面,屏蔽了所有的復(fù)雜性。
在Spring這邊,優(yōu)化框架和服務(wù)之間的交互要困難得多。比如,如果要使用Spring的聲明式事務(wù)服務(wù)來(lái)管理Hibernate事務(wù),必須手工顯式的在XML配置文件中配置Spring的TransactionManager和Hibernate的SessionFactory對(duì)象。Spring應(yīng)用的開(kāi)發(fā)者必須顯式的管理那些跨越多個(gè)HTTP請(qǐng)求的事務(wù)。另外,Spring沒(méi)有簡(jiǎn)單的利用集群的方法。
3. 服務(wù)裝配靈活性 (Flexibility in Service Assembly)
因?yàn)镾pring中的服務(wù)集成代碼是以編程接口的形式發(fā)布的,所以應(yīng)用開(kāi)發(fā)者在裝配服務(wù)時(shí)可以獲得極大的靈活性。這使得開(kāi)發(fā)者可以裝備自己的輕量級(jí)的應(yīng)用服務(wù)。Spring 的一個(gè)最普遍的應(yīng)用就時(shí)粘合Tomcat和Hibernate來(lái)支撐簡(jiǎn)單的數(shù)據(jù)庫(kù)驅(qū)動(dòng)應(yīng)用。在這種情況下,Spring提供事務(wù)服務(wù),Hibernate提供持久化服務(wù),這樣的配置構(gòu)成了一個(gè)迷你型的應(yīng)用。
EJB3.0應(yīng)用服務(wù)沒(méi)有給你這類可以選擇的靈活性。在大多數(shù)情況下,你將得到一套已經(jīng)打包好得特性,盡管其中有些你根本就用不著。當(dāng)然,如果應(yīng)用服務(wù)內(nèi)部是模塊化設(shè)計(jì),比如JBoss,你可以剝離這些你不用得部分。這樣得定制對(duì)一個(gè)成熟得應(yīng)用服務(wù)是非常有價(jià)值的。
當(dāng)然,如果應(yīng)用發(fā)生伸縮,范圍超出了單一服務(wù)節(jié)點(diǎn),你就應(yīng)該在應(yīng)用服務(wù)中加入其他服務(wù),如:資源池、消息隊(duì)列、集群等。當(dāng)加入這些后,Spring的解決方案已經(jīng)和EJB3.0 一樣重量級(jí)了。
使用Spring,服務(wù)裝配的靈活性使得利用模擬對(duì)象(mock object)非常容易。模擬對(duì)象可以在單元測(cè)試中代替真實(shí)的服務(wù)對(duì)象,從而使得容器外測(cè)試得以成為可能。EJB3.0應(yīng)用中,大部分組件都是簡(jiǎn)單的POJO,也很容易在容器外進(jìn)行測(cè)試。但是,那些牽涉到容器對(duì)象的測(cè)試(如:EntityManager),容器內(nèi)測(cè)試才是最佳選擇,比使用模擬對(duì)象測(cè)試更簡(jiǎn)單、更健壯、更準(zhǔn)確。
4. XML v.s 屬性注釋(XML Versus Annotation)
再應(yīng)用開(kāi)發(fā)者看來(lái),Spring的編程接口主要是基于XML配置文件的基礎(chǔ)上,而EJB3.0則大量使用Java注釋。XML文件可以表達(dá)復(fù)雜的(從屬)關(guān)系,但是也非常冗長(zhǎng)而且不夠健壯。注釋則比較簡(jiǎn)單,但卻很難表達(dá)復(fù)雜關(guān)系和子屬結(jié)構(gòu)。
Spring和EJB3.0分別對(duì)XML配置文件和Java注釋的選擇是由兩種框架背后的構(gòu)架設(shè)計(jì)所決定的。因?yàn)樽⑨屩荒苋菁{非常少量的配置信息,所以只有預(yù)集成的框架(大多少工作已經(jīng)在框架內(nèi)部完成)可以大量使用注釋作為其配置解決方案。正如同我們已經(jīng)討論的那樣,EJB3.0符合這種要求,而Spring,作為一種DI框架,就不能這么做。
當(dāng)然,EJB3.0和Spring都在發(fā)展中都相互借鑒了對(duì)方的長(zhǎng)處,都在不同的程度上支持XML配置或Java注釋配置。XML配置文件在EJB3.0中也被用來(lái)配置注釋的缺省行為。而在Spring中,注釋也用來(lái)配置Spring服務(wù)。
5. 聲明式服務(wù)(Declarative Services)
Spring和EJB3.0都為企業(yè)應(yīng)用提供運(yùn)行時(shí)服務(wù),(如:事務(wù)、安全、日志消息、配置服務(wù))。由于這些服務(wù)都不是直接與應(yīng)用的業(yè)務(wù)邏輯相關(guān)聯(lián),所以都不是由應(yīng)用來(lái)自行管理。事實(shí)上,這些服務(wù)由服務(wù)容器來(lái)進(jìn)行運(yùn)行時(shí)管理。開(kāi)發(fā)者(或系統(tǒng)管理員)配置容器來(lái)決定何時(shí)以及如何申請(qǐng)這些服務(wù)。
EJB3.0使用Java注釋來(lái)配置聲明式服務(wù),Spring使用XML配置文件。在大多數(shù)情況下,EJB3.0的注釋聲明顯得更為簡(jiǎn)單和優(yōu)雅。以下是一個(gè)例子,在EJB3.0的一個(gè)POJO方法中通過(guò)注釋申請(qǐng)事務(wù)支持。
public class Foo {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public bar () {
// do something ...
}
}
可以對(duì)同一代碼段定義多個(gè)屬性,從而申請(qǐng)多個(gè)運(yùn)行時(shí)服務(wù),例子如下:
@SecurityDomain("other")
public class Foo {
@RolesAllowed({"managers"})
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public bar () {
// do something ...
}
}
使用XML來(lái)定義屬性并配置聲明式服務(wù)的結(jié)果將式一個(gè)冗長(zhǎng)而不穩(wěn)定的配置文件。以下即是一個(gè)例子,通過(guò)一個(gè)XML元素為一個(gè)Spring應(yīng)用中Foo.bar()方法聲明Hibernate事務(wù)支持。
<!-- 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>
如果對(duì)同一POJO加入更多的攔截點(diǎn),XML文件的復(fù)雜性將成幾何倍數(shù)增加。意識(shí)到只使用XML來(lái)進(jìn)行配置的局限性,Spring也支持使用Apache Commons元數(shù)據(jù)在Java源碼中定義事務(wù)屬性。要使用事務(wù)元數(shù)據(jù),你需要改變transactionAttributeSource的實(shí)現(xiàn)為AttributeTransactionAttributeSource,并且添加新的元數(shù)據(jù)攔截點(diǎn)。
<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元數(shù)據(jù)簡(jiǎn)化了transactionAttributeSource元素,這在應(yīng)用中擁有很多需事務(wù)支持的方法時(shí)很有效。但是,這沒(méi)有根本解決XML配置文件的基本問(wèn)題,即:冗長(zhǎng)。并且攔截點(diǎn)transactionManager和transactionAttributeSource的配置依然不能省略。
6. 依賴注入 (Dependency Injection)
使用中間件容器的一個(gè)關(guān)鍵優(yōu)點(diǎn)是可以使得開(kāi)發(fā)者實(shí)現(xiàn)松散耦合的應(yīng)用。服務(wù)客戶端只需要知道服務(wù)接口而不是實(shí)現(xiàn)。容器承載具體的服務(wù)對(duì)象實(shí)現(xiàn)供客戶端訪問(wèn)。這允許容器在不同的服務(wù)實(shí)現(xiàn)之間切換,而接口和客戶端代碼不需要任何的修改和變動(dòng)。
依賴注入模式(以下簡(jiǎn)稱DI)是在應(yīng)用中實(shí)現(xiàn)松散耦合的的最佳實(shí)踐。相對(duì)于傳統(tǒng)的JNDI lookup方式、容器回調(diào)方式,DI模式更優(yōu)雅、更容易使用。使用DI,框架完全起著一個(gè)對(duì)象工廠的作用,在應(yīng)用中創(chuàng)建服務(wù)對(duì)象、依照運(yùn)行時(shí)配置給POJO注入服務(wù)對(duì)象。在應(yīng)用開(kāi)發(fā)者看來(lái),當(dāng)需要時(shí),客戶端POJO能夠自動(dòng)的獲得正確的服務(wù)對(duì)象。
Spring和EJB3.0都支持DI模式,但他們有著深刻的不同。Spring支持通用的(但復(fù)雜的)基于XML配置文件的依賴注入API。EJB3.0支持注入大多數(shù)服務(wù)對(duì)象(如EJB和上下文對(duì)象)和通過(guò)簡(jiǎn)單注釋聲明的JNDI對(duì)象。
EJB3.0的DI注釋異常的簡(jiǎn)潔和易用。@Resource標(biāo)簽注入大多數(shù)普通服務(wù)對(duì)象和JNDI對(duì)象。以下的例子顯示如何從JNDI注入服務(wù)的缺省DataSource對(duì)象到一個(gè)POJO中去,DefaultDS是JNDI中DataSource的一個(gè)名字。myDb變量是在第一次使用前一個(gè)自動(dòng)分配得修正變量。
public class FooDao {
@Resource (name="DefaultDS")
DataSource myDb;
// Use myDb to get JDBC connection to the database
}
作為補(bǔ)充,EJB3.0中@Resource注釋屬性也可以通過(guò)setter方法來(lái)設(shè)置。下例注入一個(gè)會(huì)話上下文。應(yīng)用程序無(wú)需顯式的去調(diào)用setter方法,而只是由容器在其他方法被調(diào)用之前引入。
@Resource
public void setSessionContext (SessionContext ctx) {
sessionCtx = ctx;
}
對(duì)于更復(fù)雜的服務(wù)對(duì)象,某些特殊的注入注釋并不可用。如:@EJB用于注入EJB Stub,而@PersistenceContext用于注入EntityManager對(duì)象,以便為EJB3.0實(shí)體bean處理數(shù)據(jù)訪問(wèn)。下例顯示如何向一個(gè)有狀態(tài)的session bean注入一個(gè)EntityManager。@PersistenceContext注釋的type屬性定義了注入的EntityManager對(duì)象擁有一個(gè)擴(kuò)展的事務(wù)上下文。這代表事務(wù)不會(huì)由JTA事務(wù)管理器自動(dòng)的提交,從而可以在一個(gè)會(huì)話中跨線程的使用。
@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規(guī)范定義了可以通過(guò)注釋去注入的服務(wù)資源。但不支持定義用戶定義的應(yīng)用POJO被注入到其他應(yīng)用中。
在Spring中,你首先需要為你POJO中的服務(wù)對(duì)象定義一個(gè)setter方法,(或帶參數(shù)的構(gòu)造函數(shù))。下例顯示POJO需要應(yīng)用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);
}
}
然后,你就可以定義容器如何去取得服務(wù)對(duì)象,并在運(yùn)行時(shí)POJO與XML元素關(guān)聯(lián)起來(lái)。下例顯示通過(guò)XML元素定義將數(shù)據(jù)源與Hibernate session factory聯(lián)系起來(lái),session factory和一個(gè)Hibernate臨時(shí)對(duì)象聯(lián)系起來(lái),并最終和應(yīng)用POJO對(duì)象聯(lián)系起來(lái)。Spring代碼的復(fù)雜性有部分來(lái)自于必須人工處理這些注入的底層,這個(gè)EJB3.0的EntityManager通過(guò)服務(wù)器自動(dòng)的管理配置形成對(duì)比。但Spring沒(méi)有向EJB3.0那樣和服務(wù)緊密集成。
<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的依賴注入語(yǔ)法復(fù)雜,但非常強(qiáng)大。你可以在你應(yīng)用中的任意POJO注入任何POJO,包括自定義的。如果你真的很希望在EJB3.0的應(yīng)用中使用Spring的DI能力,你可以通過(guò)JNDI向EJB中注入Spring bean factory。在某些EJB3.0應(yīng)用服務(wù)中,廠商可能會(huì)定義額外的非標(biāo)準(zhǔn)的API來(lái)注入POJO。一個(gè)很好的例子是JBoss MicroContainer,甚至比Spring更普遍的支持AOP。
7. 原文結(jié)論
盡管Spring和EJB3.0都瞄準(zhǔn)提供低藕合POJO的企業(yè)服務(wù),但使用了非常不同的方法來(lái)實(shí)現(xiàn)。DI模式在兩種框架中都得到大量的使用。
EJB3.0是基于標(biāo)準(zhǔn)化的實(shí)現(xiàn),大量使用注釋,和應(yīng)用服務(wù)緊密集成。帶來(lái)的結(jié)果是廠商獨(dú)立和開(kāi)發(fā)者生產(chǎn)力的提高。Spring,使用依賴注入,并且圍繞XML配置文件,給開(kāi)發(fā)者帶來(lái)很大的靈活性,并可以同時(shí)在不同的應(yīng)用服務(wù)上運(yùn)行。
附錄:
原文:POJO Application Frameworks: Spring Vs. EJB 3.0