摘要:
Spring框架雖然很流行但并不是一個標準的開源框架。EJB3.0是由Java Community Process (JCP)制訂的標準框架.這兩個框架結構都有一個共同核心設計理念:將中間件服務傳遞給耦合松散的POJOS (Plain Old Java Objects, 簡單潔凈Java對象)。 本文將對Srping和EJB3.0框架背后的關鍵不同處進行考察,并討論其優缺點。本文的觀點也適用于其它更少為人知的框架,因為他們都是對“耦合松散的POJO”的設計
POJO應用框架:Spring與EJB3.0的比較作者:Michael Juntao Yuan06/29/2005翻譯:loryliu版權聲明:可以任意轉載,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
英文原文地址:
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這個開源項目的支持者。