摘要:
Spring框架雖然很流行但并不是一個標(biāo)準(zhǔn)的開源框架。EJB3.0是由Java Community Process (JCP)制訂的標(biāo)準(zhǔn)框架.這兩個框架結(jié)構(gòu)都有一個共同核心設(shè)計理念:將中間件服務(wù)傳遞給耦合松散的POJOS (Plain Old Java Objects, 簡單潔凈Java對象)。 本文將對Srping和EJB3.0框架背后的關(guān)鍵不同處進行考察,并討論其優(yōu)缺點。本文的觀點也適用于其它更少為人知的框架,因為他們都是對“耦合松散的POJO”的設(shè)計版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
英文原文地址:
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
關(guān)鍵詞: Spring EJB
艾伯特.愛因斯坦曾經(jīng)說過:“一切都應(yīng)該盡可能地簡單,但是不能更簡單?!贝_實如此,簡化一門理論的基本假設(shè),使我們可以專注于真正關(guān)鍵的地方,這正是一直以來對科學(xué)真理的追求。企業(yè)軟件開發(fā)同樣如此。
提供一個將復(fù)雜的事物(例如,事務(wù)、安全或持久性)對開發(fā)者進行隱藏的應(yīng)用框架是簡化企業(yè)軟件開發(fā)的關(guān)鍵。一個設(shè)計良好的框架可以提高代碼重用率、開發(fā)者的生產(chǎn)力及軟件的質(zhì)量。然而,現(xiàn)有J2EE1.4的EJB2.1框架被普遍認(rèn)為設(shè)計差,且過于復(fù)雜。不滿于EJB2.1的框架結(jié)構(gòu),Java開發(fā)者嘗試了各種各樣的中間件服務(wù)傳遞方法。最引人注目的是,以下兩個框架引起了開發(fā)者極大興趣并得到了大量正面的反饋。他們以未來企業(yè)Java應(yīng)用所選框架的姿態(tài)展現(xiàn)。
????????Spring框架雖然很流行但并不是一個標(biāo)準(zhǔn)的開源框架。它主要由Interface21 Inc開發(fā)和控制。Spring框架結(jié)構(gòu)是基于依賴注入(Dependency Injection (DI))的設(shè)計模式。它可以獨立或在現(xiàn)有的應(yīng)用服務(wù)器上運行,而且大量地使用了xml配置文件
????????EJB3.0是由Java Community Process (JCP)制訂的標(biāo)準(zhǔn)框架,為所有主要的J2EE廠商支持。JBoss已經(jīng)提供了試用版EJB3.0標(biāo)準(zhǔn)的開源或商業(yè)性質(zhì)實現(xiàn)。EJB3.0充分利用了Java的注釋
這兩個框架結(jié)構(gòu)都有一個共同核心設(shè)計理念:將中間件服務(wù)傳遞給耦合松散的POJOS (Plain Old Java Objects, 簡單潔凈Java對象)。 這樣的框架利用截取執(zhí)行上下文或在運行時將服務(wù)對象注入POJO來把應(yīng)用服務(wù)“纏繞”到POJO。POJO本身并不關(guān)心這種“纏繞”,對這種框架結(jié)構(gòu)也沒有什么依賴。因此,開發(fā)者可專注于業(yè)務(wù)邏輯和脫離框架的POJO單元測試。除此之外, 由于POJO并不須要繼承框架的類或?qū)崿F(xiàn)其接口,開發(fā)者能夠極其靈活地搭建繼承結(jié)構(gòu)和建造應(yīng)用。
然而,在擁有同一理念的同時,兩個框架結(jié)構(gòu)使用不同的方式來傳遞POJO服務(wù)。許多書籍或文章都將Spring 或EJB3.0和EJB2.1做了比較,但是對Spring 和EJB3.0的比較并沒有仔細(xì)研究過。在本文中,我將對Srping和EJB3.0框架背后的關(guān)鍵不同處進行考察,并討論其優(yōu)缺點。本文的觀點也適用于其它更少為人知的框架,因為他們都是對“耦合松散的POJO”的設(shè)計。希望這篇文章可以幫助你選擇適合你需求的最好框架。
廠商無關(guān)性
開發(fā)者選擇Java平臺其中最引人注目的理由之一:廠商無關(guān)性。EJB3.0正是一套設(shè)計為廠商無關(guān)的開放性標(biāo)準(zhǔn)。EJB3.0標(biāo)準(zhǔn)為所有企業(yè)Java社團里開源或商業(yè)性質(zhì)廠商所開發(fā)和支持。它將開發(fā)者與應(yīng)用服務(wù)器實現(xiàn)完全隔離。例如,JBoss的 EJB3.0實現(xiàn)基于Hibernate,Oracle的基于TopLink,但是開發(fā)者并不須要學(xué)習(xí)Hibernate- 或TopLink的具體API來使應(yīng)用可在Jboss或Oracle上運行。廠商無關(guān)性使EJB3.0與現(xiàn)今其它POJO中間件框架區(qū)別開來。
但是,正如許多EJB3.0評論家迅速所指出的,在本文撰寫時EJB3.0標(biāo)準(zhǔn)還沒有到達(dá)一個最終版本。大概還有一到兩年的時間EJB3.0才能廣泛地為所有主要J2EE廠商所支持。即使你的應(yīng)用服務(wù)器本身不支持EJB3.0,你仍然可以通過下載安裝”內(nèi)嵌的”EJB3.0產(chǎn)品來運行EJB3.0的應(yīng)用。例如,JBoss的內(nèi)嵌EjB3.0是開源產(chǎn)品且可以在任何J2SE5.0兼容的環(huán)境運行(例如, 在任何Java服務(wù)器上),此產(chǎn)品正處于軟件測試階段。其它廠商不久也將發(fā)布自己的內(nèi)嵌EJB3.0產(chǎn)品,特別是針對標(biāo)準(zhǔn)中關(guān)于數(shù)據(jù)持久性的部分。
另一方面,Spring一直以來都是非標(biāo)準(zhǔn)的技術(shù),在未來可預(yù)知的一段時間內(nèi)這種情況將持續(xù)下去。雖然你可以在任何應(yīng)用服務(wù)器上使用Spring框架,Spring應(yīng)用會被鎖入在Spring本身和你選擇整合進Spring的具體服務(wù)中。
????????Spring框架是一個開源項目,但同時它有一個XML格式的配置文件和編程接口。當(dāng)然任何一個非標(biāo)準(zhǔn)的產(chǎn)品都會有這種“鎖入”(lock-in)的情況,并不是Spring特有的。但Spring應(yīng)用的長期生存能力仍然還得托Spring這個項目的福(或者是Interface21公司,它雇傭了大部分Spring核心開發(fā)人員)。除此之外,假如你用到任何一個具體的Spring服務(wù),例如,Spring事務(wù)管理器或則Spring MVC,你也會被鎖入到這些API里。
????????Spring的應(yīng)用對終端用戶是不可知的。例如,對數(shù)據(jù)持久服務(wù),Spring框架兼容不同的DAO和JDBC的模版幫助類,如Hibernate, iBatis, 和 JDO。所以假如你需要為spring應(yīng)用切換在數(shù)據(jù)持久化服務(wù)(例如從JBDC到Hibernate),你需要修改你的代碼以適合新的模版幫助類。
服務(wù)整合
從一個很高的角度上看,Spring框架處于應(yīng)用服務(wù)器和服務(wù)庫的上方。服務(wù)整合的代碼(如,數(shù)據(jù)訪問模板和幫助類)屬于框架,并暴露于應(yīng)用開發(fā)者。相反,EJB3.0框架與應(yīng)用服務(wù)器高度整合,服務(wù)整合代碼也包裝在一個標(biāo)準(zhǔn)接口后面。
因此,實現(xiàn)EJB3.0的廠商可以大大地優(yōu)化整體性能和提升開發(fā)者的體驗。例如,在JBoss EJB3.0的實現(xiàn)中,當(dāng)你在用EntityManager持久化一個Entity Bean時,后臺的Hibernate會話事務(wù)已經(jīng)自動地幫定到調(diào)用方法的JTA 的事務(wù)上,在JTA 事務(wù)提交的同時Hibernate會話事務(wù)也提交了。你甚至可以使用一個簡單的 @PersistenceContext 注釋(稍候例子演示)將EntityManager和它后臺的Hibernate事務(wù)綁定到一個stateful session bean的應(yīng)用事務(wù)中。在一個會話中應(yīng)用事務(wù)橫跨多個線程,這在事務(wù)性網(wǎng)頁應(yīng)用很有用,例如,多頁面的購物車。
由于高度整合的EJB3.0的框架,使簡單、集成的編程接口成為可能。Oracle EJB3.0框架和其后臺的Toplink持久化服務(wù)也同樣程度地整合。
另一個EJB3.0整合服務(wù)的絕好例子就是集群支持。假如你在一個服務(wù)器集群上部署了一個EJB3.0的應(yīng)用,所有容錯(fail-over)、負(fù)載均衡、分布式緩沖和狀態(tài)復(fù)制都已經(jīng)自動為應(yīng)用所獲得可用。后臺的集群支持被隱藏在EJB3.0的框架后面,對EJB3.0開發(fā)者來說這些都是完全透明不可見的。
在Spring里,很難優(yōu)化框架和服務(wù)之間的通訊。例如,為了使用Spring里的聲明事務(wù)服務(wù)來管理Hibernate事務(wù),你必須顯示地在XML文件中配置Spring TransactionManager和Hibernate SessionFactory對象。Spring必須電顯示地管理橫跨多個HTTP請求的事務(wù)。除此之外,沒有別的方法均衡Spring應(yīng)用里的集群。
服務(wù)組合的彈性
由于Spring的服務(wù)整合代碼作為編程接口的一部份暴露在外,應(yīng)用開發(fā)者有按自己需求裝配服務(wù)的彈性。這個特點使你能夠組合自己的輕量級應(yīng)用服務(wù)器。Spring的一個普遍用法就是將Tomcat和Hibernate組合在一起支持?jǐn)?shù)據(jù)庫驅(qū)動的web應(yīng)用。在這種情況,Spring本身提供事務(wù)服務(wù),Hibernat提供持久化服務(wù)——這種設(shè)置創(chuàng)建了一個袖珍型的應(yīng)用服務(wù)器。
EJB3.0應(yīng)用服務(wù)器典型地不提供這種根據(jù)需求任你挑撿服務(wù)的彈性空間。大多數(shù)時間,你得到的只是一系列包裝好的特性,其中一些你可能根本就不需要。但是如果應(yīng)用服務(wù)器像JBoss一樣提供一個模塊性的內(nèi)部設(shè)計,那么你可以只取其中一部分,而把不必要的部分剝?nèi)?。在任何情況,去自定義一個功能強大的應(yīng)用服務(wù)器是沒有什么價值的。
當(dāng)然,假如應(yīng)用已經(jīng)超過單個點,那么你應(yīng)該加入常用服務(wù)器上的服務(wù),例如,資源池(resource pooling),消息隊列(message queuing)和集群(clustering)。就總體的資源消耗而言,Spring解決方法和其他EJB3.0解決方法一樣是重量級的。
在Spring框架里,具有彈性的服務(wù)裝配使得將虛擬對象而不是真正的業(yè)務(wù)對象綁定到應(yīng)用中做脫離容器的單元測試更簡單。在EJB3.0應(yīng)用中,大多數(shù)組件都是簡單POJO,他們可以很容易地在容器外被測試。但是對于與容器服務(wù)相關(guān)的對象(例如持久化實實體管理器EntityManager)建議用容器內(nèi)測試。因為這樣會比虛擬對象測試方法更簡單,強壯及準(zhǔn)確。
XML Vs.注解
從應(yīng)用開發(fā)者的觀點上來看,Spring的編程開發(fā)接口主要基于XML配置文件而EJB3.0廣泛地應(yīng)用Java注解。XML可以表達(dá)復(fù)雜的關(guān)系,但是它也冗長且不夠健壯;注解簡單明了,但是很難在注解里表達(dá)復(fù)雜或繼承性的關(guān)系。
Spring選擇XML或EJB3.0選擇注解都是有他們兩者框架后的體系結(jié)構(gòu)決定的。因為注解只能容納很少的配置信息,只有整合前的框架(重頭戲都在框架里)才可以把廣泛地使用注解作為配置選擇。正如我們所討論過的,EJB3.0剛好符合這個要求,而Spring作為一個普通的DI框架并不符合。
當(dāng)然,EJB3.0和Spring都相互取長補短,在某種程度上他們都支持XML和注解。例如,在EJB3.0中,XML配置文件作為一個可選的重載機制來改變注解的默認(rèn)行為。注解也可以配置一些Spring服務(wù)。
通過例子是學(xué)習(xí)XML和注解方式之間差異的最好方法。在下面幾個環(huán)節(jié)里,讓我們來看看Spring和EJB3.0是怎樣提供關(guān)鍵服務(wù)給應(yīng)用的。
聲明性服務(wù)
Spring和EJB3.0都將運行時服務(wù)(例如,事務(wù)、安全、日志和配置服務(wù))綁定到應(yīng)用。因為這些服務(wù)于應(yīng)用的業(yè)務(wù)邏輯是沒有直接聯(lián)系,他們只是由應(yīng)用本身管理。換句話說,這些服務(wù)在運行時由容器透明地應(yīng)用到應(yīng)用中。開發(fā)者或是管理者配置容器,準(zhǔn)確地告訴它什么時候怎樣應(yīng)用這些服務(wù)。
EJB3.0運用Java注解來配置聲明性服務(wù),而Sring使用XML配置文件。在大多數(shù)情況下,EJB3.0注解方式對于這種服務(wù)更簡單明了。這里有一個在EJB3.0中將事務(wù)服務(wù)運用到POJO的例子。
public class Foo {
????
????@TransactionAttribute(TransactionAttributeType.REQUIRED)
????public bar () {
??????// do something ...
????}????
}
你也可以為一個代碼段聲明多個屬性,應(yīng)用多個服務(wù)。這是一個在EJB3.0里同時應(yīng)用事務(wù)和安全服務(wù)到POJO的例子。
@SecurityDomain("other")
public class Foo {
????
????@RolesAllowed({"managers"})
????@TransactionAttribute(TransactionAttributeType.REQUIRED)
????public bar () {
??????// do something ...
????}??
}
使用XML說明代碼屬性和配置聲明性服務(wù)會導(dǎo)致冗長和不穩(wěn)定的配置文件。下面是一個在Spring應(yīng)用中的XML片段,其應(yīng)用一個非常簡單的Hibernate事務(wù)到方法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的復(fù)雜度會以幾何級數(shù)增長,如果你向同一個POJO添加更多的攔截器(interceptors)(例如安全攔截器)。意識到只有XML配置文件的局限,Spring使用Apache Commons 元數(shù)據(jù)在Java源碼中來說明事務(wù)屬性。最新版本的Spring1.2也支持JDK-1.5風(fēng)格注解。要使用事務(wù)元數(shù)據(jù),你須要將上面的transactionAttributeSourc bean變成一個AttributesTransactionAttributeSource實例。并為元數(shù)據(jù)攔截器添加額外邦定。
????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"/>
當(dāng)你有很多事務(wù)性方法時,Spring元數(shù)據(jù)可以簡化transactionAttributeSource。但是這并沒有解決XML配置文件的根本問題。冗長而又繁瑣的事務(wù)攔截器, transactionManager,和transactionAttributeSource仍然需要。
依賴注入(Dependency Injection, DI)
中間件容器的一個關(guān)鍵好處之一就是它可以讓開發(fā)者建造一個關(guān)系耦合松散的應(yīng)用。服務(wù)端客戶只需要知道服務(wù)的接口。容器依據(jù)具體的實現(xiàn)實例化服務(wù)對象,使他們?yōu)榭蛻舳怂谩T诓桓淖兘涌诤涂蛻舳舜a的情況下,這使得容器可以在多種服務(wù)實現(xiàn)之間切換。
依賴注入的模式是實現(xiàn)耦合松散應(yīng)用的最好方法之一。它更易用,比其他方法也明了多了,比如通過JNDI依賴性查詢或容器回調(diào)。使用DI,框架就像一個對象工廠,它創(chuàng)建服務(wù)對象然后按照運行時配置將這些服務(wù)對象注入到應(yīng)用的POJO里。站在應(yīng)用開發(fā)者的角度,客戶端POJO在被使用時可自動獲得正確的服務(wù)對象。
Spring和EJB3.0都提供廣泛的DI模式支持。但是他們之間仍存在很大的不同之處。Spring支持一般意義上且復(fù)雜的DI API,其基于XML配置文件。EJB3.0支持大多數(shù)普通服務(wù)對象(如EJB及context對象)的注入和任何簡單注解的JDNI。
EJB63.0注解非常簡單易用。@Resource 標(biāo)記表示注入大多數(shù)普通服務(wù)對象和JDNI對象。以下例子展示了怎樣把服務(wù)的JDNI的默認(rèn)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對象。應(yīng)用從不會顯示地調(diào)用setter方法,其在其他方法被調(diào)用前由容器來觸發(fā)。
@Resource
public void setSessionContext (SessionContext ctx) {
????sessionCtx = ctx;
}
針對更復(fù)雜的服務(wù)對象,定義了專用的注入注解。例如,@EJB注釋用來注入EJB的Stub,@PersistenceContext注解用來注入處理EJB3.0實體bean訪問數(shù)據(jù)庫的EntityManager對象。下面是一個怎樣將EntityManager對象注入有狀態(tài)的 session bean的例子。@PersistenceContext的type屬性具體說明了被注入的EntityManager有一個擴展的事務(wù)transaction context。transaction context并不會同JTA transaction manager一起自動提交。因此它可以用在在一個會話橫跨多個線程的應(yīng)用事務(wù)中。
@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標(biāo)準(zhǔn)通過注解可以被注入的服務(wù)器資源。但是它并支持將用戶定義的應(yīng)用POJO之間的相互注入。
在Spring中,首先你必須為POJO中的服務(wù)對象定義一個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里的元素作為橋梁具體描述容器怎樣在運行時得到服務(wù)對象并將其注入到POJO里。以下是一個XML例子,具體描述了將一個數(shù)據(jù)源綁定到一個Hibernate session factory,然后從Hibernate session factory到Hibernate template object,最后從template object到應(yīng)用的POJO。Spring代碼如此復(fù)雜的部分原因是因為我們須手手動注入后臺Hibernate plumbing objects。而EJB3.0 EntityManager是自動被服務(wù)器管理和配置。這又將我們帶回到Spring并不像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的依賴注入語法復(fù)雜,但卻功能強大。你可以將任何POJO注入到另一個POJO,包括你自己在應(yīng)用定義的那些POJO。假如你想在EJB3.0應(yīng)用中用Spring的DI功能 ,你可以通過JNDI把一個Spring bean factory注入到EJB。在一些EJB3.0的應(yīng)用服務(wù)器里,廠商可能會額外定義非標(biāo)準(zhǔn)的POJO注入API。一個很好的例子就是JBoss MicroContainer。它比Spring更一般化,因為它處理Aspect-Oriented Programming(AOP)的依賴。
結(jié)論
Spring和Ejb3.0雖然都是為了向企業(yè)服務(wù)提供耦合松散的POJO,但是使用了不同方法來達(dá)到這個目的。兩者都大量地使用了依賴注入。
對于EJB3.0,基于標(biāo)準(zhǔn)的方案、注解的廣泛使用、與應(yīng)用服務(wù)器的高度整合都使得EJB3.0擁有更好的廠商無關(guān)性,更高的開發(fā)效率。依賴注入和集中的XML配置文件協(xié)調(diào)一致的使用使開發(fā)者能夠構(gòu)建更有彈性的應(yīng)用,并且可以同時和幾個應(yīng)用服務(wù)提供者一起協(xié)作。
鳴謝
作者感謝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的企業(yè)解決方案, 也是一個移動方面的專家,是avid這個開源項目的支持者。