愛因斯坦曾經(jīng)說過:"每件事物都應該盡可能簡單,而不是更簡單"。的確,對科學真理的追求都是為了簡化理論的根本假設,這樣我們才能處理真正麻煩的問題。企業(yè)級軟件的開發(fā)也是這樣的。
簡化企業(yè)級軟件開發(fā)的關鍵是提供一個隱藏了復雜性(例如事務、安全性和永續(xù)性)的應用框架。良好設計的框架組件可以提升代碼的重復使用(reuse)能力,提高開發(fā)效率,從而得到更好的軟件質(zhì)量。但是,目前j2ee 1.4中的ejb 2.1框架組件被人們普遍認為是設計較差的和過于復雜的。java開發(fā)者對ejb 2.1很不滿,他們已經(jīng)試驗了多種其它的用于中間件服務傳送的方法。最引人注目的,下面兩個框架組件已經(jīng)引起開發(fā)者的巨大興趣和積極的反映。它們很可能成為未來企業(yè)級java應用程序可供選擇的框架組件。
· spring框架組件是一個流行的,但是非標準的開放源代碼框架組件。它主要是由interface21 inc.公司開發(fā)和控制的。spring框架組件的架構(gòu)是基于依賴注入(di)設計模式的。spring可以單獨地或者與現(xiàn)有的應用程序服務器一起工作,它大量地使用xml配置文件。
· ejb 3.0框架組件是一個標準的框架組件,由java社區(qū)組織(jcp)定義,并受到所有主流的j2ee廠商支持。預發(fā)布的ejb 3.0規(guī)范的開放源代碼和商業(yè)實現(xiàn)都可以在jboss和oracle上看到了。ejb 3.0大量使用java注釋(annotation)。
這兩個框架組件的核心設計理念是相同的:兩者的目標都是把中間件服務傳遞給松散耦合的簡單舊式java對象(pojo)。這些框架組件通過在運行時截取執(zhí)行內(nèi)容或向pojo注入服務對象,把應用程序服務與pojo捆綁在一起。pojo本身不關心捆綁的過程,并且對框架組件幾乎沒有依賴。其結(jié)果是,開發(fā)者可以聚焦于業(yè)務邏輯,個人可以在沒有框架組件的情況下測試他們的pojo。此外,由于pojo不需要從框架組件中繼承或?qū)崿F(xiàn)框架組件接口,開發(fā)者建立繼承結(jié)構(gòu)和構(gòu)建應用程序的時候都有高度的靈活性。
  但是,盡管兩者的設計理念是相同的,它們傳遞pojo服務時卻采用了完全不同的方法。盡管目前已經(jīng)出版了大量的圖書和文章來把spring或ejb 3.0與ejb 2.1進行對比,但是它們都沒有對spring與ejb 3.0之間的差異進行認真的研究。在本文中,我將研究spring和ejb 3.0框架組件之間的關鍵差異,并討論它們的優(yōu)缺點。本文的主題也可以應用在其它一些名氣稍小的企業(yè)級中間件框架組件上,因為它們都聚焦于"松散耦合的pojo"設計。我希望本文能夠幫助你選擇符合需求的最佳的框架組件。 
  廠商無關性(independence)
開發(fā)者選擇某種java平臺的一個最重要的理由就是該平臺的廠商無關性。ejb 3.0是一個開放的、標準的、具有廠商無關性的平臺。ejb 3.0規(guī)范是由企業(yè)級java團體中所有主流開放源代碼和商業(yè)廠商開發(fā)和支持的。ejb 3.0框架組件把開發(fā)人員與應用程序服務器實現(xiàn)(implementation)隔離開來了。例如,盡管jboss的ejb 3.0實現(xiàn)是基于hibernate的,而oracle的ejb 3.0實現(xiàn)是基于toplink的,但是開發(fā)人員并不需要學習hibernate或toplink的特殊api,就可以讓他們的應用程序在jboss和oracle上運行。廠商無關性把ejb 3.0框架組件與其它的pojo中間件框架組件區(qū)分開來了。
但是,很多ejb 3.0的批評家迅速指出,在寫這篇文章的時候,ejb 3.0規(guī)范還沒有達到最終發(fā)表的版本。在ejb 3.0被所有主流的j2ee廠商采用之前可能還需要一到兩年時間。但是,即使你的應用程序服務器還沒有自然地(natively)支持ejb 3.0,你還是可以通過下載和安裝一個"嵌入式的" ejb 3.0產(chǎn)品,在服務器上運行ejb 3.0應用程序。例如,jboss嵌入式ejb 3.0產(chǎn)品是開放源代碼的,可以在任何與j2se-5.0兼容的環(huán)境中(例如,在java應用程序服務器中)運行。它現(xiàn)在正在進行beta測試。其它的廠商也可能很快發(fā)布他們的嵌入式ejb 3.0產(chǎn)品,特別是用于規(guī)范的"數(shù)據(jù)永續(xù)性"部分的產(chǎn)品。
另一方面,spring一直是非標準的技術(shù),而且在可以預見的未來它仍然是這樣的。盡管你可以把spring框架組件與任何應用程序服務器一起使用,但是spring應用程序都被"鎖定"在spring自身和你所選擇的集成到spring中的特定服務中了。
· 盡管spring框架組件是一個開放源代碼項目,但是它仍然擁有配置文件的專利xml格式和專利編程接口。當然,這類"鎖定"發(fā)生在任何非標準的產(chǎn)品上,spring也不例外。但是它卻造成了:你的spring應用程序的長期生存能力依賴于spring項目本身(或interface21 inc公司,它雇傭了大多數(shù)spring核心開放人員)。此外,如果你使用任何spring特定的服務,例如spring事務管理器或spring mvc,你就被"鎖定"在這些api中了。
· spring應用程序需要知道后臺的服務提供者。例如,對于數(shù)據(jù)持續(xù)(data persistence)服務來說,spring框架組件為jdbc、hibernate、ibatis和jdo使用了不同的dao和模板輔助類。因此,如果你希望為spring應用程序更換持續(xù)服務提供者(例如從jdbc切換到hibernate),你就必須重構(gòu)自己的應用程序代碼,使用新的輔助類。
服務集成
從較高的層次看,spring框架組件位于應用程序服務器和服務類庫之上。其服務集成代碼(例如數(shù)據(jù)訪問模板和輔助類)位于框架組件之中,并暴露給應用程序開發(fā)者。與此不同的是,ejb 3.0框架組件被緊密地集成到應用程序服務器中,服務集成代碼被封裝在標準的接口中。
其結(jié)果是,ejb 3.0廠商可以積極地優(yōu)化總體性能和開發(fā)者體驗。例如,在jboss的 ejb 3.0實現(xiàn)中,使用entitymanager保持實體bean pojo的時候,下層hibernate對話事務會自動地與該調(diào)用方法的jta事務聯(lián)系在一起,當jta事務提交的時候,它也會提交。如果使用簡單的@persistencecontext注釋(本文后面有一個例子),你甚至于可以在有狀態(tài)的(stateful)對話bean中把entitymanager和它的下層hibernate事務捆綁到一個應用程序事務上。該應用程序事務在一個對話中跨越了多個線程,它在事務性的web應用程序(例如多頁面購物車)中是非常有效的。由于在jboss中,ejb 3.0框架組件、hibernate和tomcat緊密集成,上述的簡單和集成的編程接口才得以實現(xiàn)。oracle的ejb 3.0框架組件和其下層toplink持續(xù)服務之間的也實現(xiàn)了類似層次的集成。
ejb 3.0中集成服務的另一個例子是群集(clustering)支持。如果你在服務器群集中部署ejb 3.0應用程序,那么所有的失效接續(xù)(fail-over)、負載均衡、分布式緩存和狀態(tài)復制服務都是可以自動地供應用程序使用的。下層群集服務都隱藏在ejb 3.0編程接口后面,它們對于ejb 3.0開發(fā)人員來說是完全透明的。
在spring中,優(yōu)化框架組件與服務之間的交互操作要困難得多。例如,為了使用spring的宣告式事務服務來管理hibernate事務,你必須在xml配置文件中顯式地配置spring transactionmanager和hibernate sessionfactory對象。spring應用程序開發(fā)者必須顯式地管理跨多個http請求的事務。此外,要在spring應用程序中使用群集服務也沒有簡單的途徑。
服務集成的靈活性
由于spring中的服務集成代碼是作為編程接口的一部分暴露的,應用程序開發(fā)者可以根據(jù)需要靈活地集成服務。這個特性允許你集成自己的"輕量級"應用程序服務器。spring最普遍的使用方式是把tomcat和hibernate"粘合"在一起來提供簡單的數(shù)據(jù)庫驅(qū)動web應用程序。在這種情況下,spring自身提供事務服務,hibernate提供持續(xù)(persistence)服務--這種組織方式在spring中建立了一個微型應用程序服務器。
ejb 3.0應用程序服務器沒有賦予你挑選服務的靈活性。在大多數(shù)情況中,你得到一組事先包裝好的特性,而你只需要其中的一部分。但是,如果應用程序服務器由模式化的內(nèi)部設計主導(類似jboss),那么你就可能把它分開,去掉一些不必要的特性。在任何情況下,定制成熟的應用程序服務器都不是一個簡單的事情。
當然,如果應用程序的范圍超越了單節(jié)點,那么你可能需要捆綁來自普通應用程序服務器的服務(例如資源緩沖池、消息隊列和群集)。在總體的資源消耗方面,spring解決方案與任何ejb 3.0解決方案一樣,都是"重量級"的。
  在spring中,靈活的服務集成使得我們更容易把仿制(mock)對象(而不是實際的服務對象)捆綁到應用程序,用于在容器外部進行單元測試。在ejb 3.0應用程序中,大多數(shù)組件都是簡單的pojo,我們可以很容易地在容器外部測試這些它們。但是對于測試那些涉及到容器服務的對象(例如持續(xù)entitymanager),我們推薦在容器內(nèi)測試,因為比起仿制對象的方法,它們更簡單、更牢固、更精確。 xml與注釋的比較
從應用程序開發(fā)者的角度來看,spring的編程接口主要是基于xml配置文件的,而ejb 3.0廣泛使用了java注釋。xml文件可以表達復雜的關系,但是它們同時也很冗長、牢固程度也較低。注釋簡單明了,但是在注釋中我們卻很難表達復雜的或?qū)哟蔚慕Y(jié)構(gòu)。
spring和ejb 3.0關于xml或注釋的選擇是依賴于這兩個框架組件后面的架構(gòu)的:由于注釋只能保存相當少的配置信息,只有預先集成的框架組件(類似在框架組件中已經(jīng)完成了大多數(shù)預備工作)可以廣泛地把注釋作為配置選項。我們已經(jīng)討論過了,ejb 3.0符合這種需求,而spring作為一個通用的di框架組件,不符合這個需求。
當然,ejb 3.0和spring都在學習對方的最佳特性,它們都在某個程度上支持xml和注釋。例如,在ejb 3.0中xml配置文件是一個可選的重載機制,可以用于改變注釋的默認行為。注釋也可以用于配置某些spring服務。
認識xml和注釋之間的區(qū)別的最好途徑是通過示例。在下一部分,我們會看到spring和ejb 3.0是如何為應用程序提供關鍵服務的。
  宣告式服務(declarative services)
  spring和ejb 3.0都把運行時服務(例如事務、安全性、日志記錄、消息和定制服務)捆綁到應用程序上。由于這些服務都沒有直接地與應用程序的業(yè)務邏輯相關聯(lián),因此它們不由應用程序自身來管理。作為代替,這些服務是在運行時由服務容器(例如spring或ejb 3.0)透明地應用在程序上的。開發(fā)者(或管理員)配置容器并告訴容器如何/什么時候應用服務。
  ejb 3.0使用java注釋配置宣告式服務,而spring使用xml配置文件。在大多數(shù)情況下,對于這類服務,ejb 3.0注釋方法更加簡單,更加優(yōu)雅。下面是一個在ejb 3.0中給pojo方法應用事務服務的例子。
public class foo {
@transactionattribute(transactionattributetype.required)
public bar () {
// 執(zhí)行某些操作 ...
} 
}
@securitydomain("other")
public class foo {
@rolesallowed({"managers"})
@transactionattribute(transactionattributetype.required)
public bar () {
// 執(zhí)行某些操作 ...
} 
}
<!-- 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>
<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"/>
  依賴注入(dependency injection)
  中間件容器的關鍵優(yōu)勢在于它們允許開發(fā)者建立松散耦合的應用程序。服務的客戶端只需要知道服務的接口。容器用具體的實現(xiàn)來初始化服務對象,并使客戶端能夠訪問它們。這就允許了容器在不同的服務實現(xiàn)之間進行切換,而不需要改變接口或客戶端代碼。
  依賴注入(di)模式是實現(xiàn)松散耦合的應用程序的最好的方法之一。它比舊方法(例如通過jndi的依賴查找或容器回調(diào))更易于使用、更優(yōu)雅。使用di的時候,框架組件充當建立服務對象的對象工廠,并根據(jù)運行時配置,把這些服務對象注入應用程序pojo中。從應用程序開發(fā)者的角度來看,當客戶端pojo需要使用某種服務對象的時候,它們會自動地獲取該對象。 
  spring和ejb 3.0都給di模式提供了廣泛的支持,但是它們之間有一些深刻的差異。spring支持普通的、但是復雜的、基于xml配置文件的di api;ejb 3.0通過簡單的注釋支持大多數(shù)通用服務對象(例如ejb和上下文關系對象)和jndi對象的注入操作。
  ejb 3.0 di注釋非常簡潔,易于使用。@resource標簽注入大多數(shù)通用服務對象和jndi對象。下面的例子演示了如何把jndi中的服務器的默認datasource對象注入pojo的一個字段變量中。defaultds是jndi用于表示datasource的名稱。在第一次使用mydb變量之前,會把正確的值自動地賦給它。 
public class foodao {
@resource (name="defaultds")
datasource mydb;
// 使用 mydb 獲取數(shù)據(jù)庫的jdbc連接
}
@resource 
public void setsessioncontext (sessioncontext ctx) { 
sessionctx = ctx; 
} 
@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);
}
}
public class foodao {
hibernatetemplate hibernatetemplate;
public void sethibernatetemplate (hibernatetemplate ht) {
hibernatetemplate = ht;
}
// 使用 hibernate 模板訪問數(shù)據(jù)
public foo getfoo (integer id) {
return (foo) hibernatetemplate.load (foo.class, id);
}
}
<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 -->
新聞熱點
疑難解答
圖片精選