EJB與Spring的集成

作爲一個輕量級的容器,Spring通常被認爲當作是EJB的替代方案。不可否認,在許多應用場合中,Spring以其卓越的性能與豐富的事務處理、ORM、JDBC存取等功能,確實可以取代EJB。不過,我們應當注意到,Spring本身並不排斥EJB,事實上,Spring還爲在框架內部存取EJB、實現EJB的功能而提供了良好的支持。而且,如果利用Spring訪問EJB所提供的服務,那麼這些服務的具體實現可以在本地EJB、遠程EJB,或者POJO之間進行透明地切換,而無需改動任何一行客戶端的程序代碼。

     在本文中,我們着重討論一下Spring訪問、實現EJB的機制,特別是對於無狀態會話bean(SLSB)的支持。

 1.如何在Spring中訪問EJB?
     在Java EE中,爲了調用一個本地或者遠程SLSB的方法,客戶端必須首先通過JNDI查找獲取一個(本地或者遠程)EJB Home對象,而後通過這個 EJB Home對象的create()方法獲取一個實際的(本地或者遠程)EJB對象,從而調用此EJB對象的各種方法。
     爲了避免底層代碼的重複,許多EJB應用程序採用了Service Locator和Business Delegate設計模式,這比在客戶端程序中使用很多JNDI查詢要好很多,但其缺陷也是十分明顯的,例如:
 *  依賴Service Locator和Business Delegate singleton的程序代碼通常是難以測試的。
 *  若只使用Service Locator模式,而不使用Business Delegate,那麼應用程序依舊必須調用一個EJB Home的craete()方法,並處理各種異常。因此,應用程序將與EJB的API與複雜編程模型緊密耦合在一起。
 *  使用Business Delegate模式通常將造成大量的重複代碼,僅僅爲了調用EJB的同一個方法,開發人員必須編寫許多程序。

      而通過Spring,則可以創建和使用在Spring內部配置好的代理,這個代理將扮演業務delegate的角色,開發人員無需編寫另外一個Service Locator和JNDI查詢代碼,也無需複製Business Delegate的方法調用,除非是在這些程序代碼確實有其他有價值的內容。

     假設有一個需要使用本地EJB的web controller,我們將採用EJB的業務方法接口模式來實現,從而EJB的本地接口可以擴展一個非EJB的業務方法接口。我們將這個業務方法暫且稱之爲MyComponent,其定義類似於:


public interface MyComponent {
...
}

     使用業務方法接口模式的一個主要原因在於保證本地接口中的方法定義和接口實現bean的方法定義自動同步,另一原因是如果需要將該方法以POJO實現,那麼這種轉化將比較容易。當然,我們還需要實現本地home接口,並提供一個實現SessionBean和MyComponet業務方法接口的類。現在,爲了將示例中的web controller與EJB實現掛鉤,我們需要編寫的唯一一段代碼就是在這個controller中對外公開一個MyComponet對象的setter,示例如下:

private MyComponent myComponent;
public void setMyComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}

      然後,便可以在controller中的任何一個業務方法中調用這個myComponet實例。假設需要在Spring容器之外獲取該controller對象,我們可以在同一環境中配置一個LocalStatelessSessionProxyFactoryBean 實例作爲EJB代理。這個代理的配置以及controller中myComponent屬性的設置,均在Spring的配置文件定義,示例如下:

<bean id="myComponent"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="myComponent"/>
    <property name="businessInterface" value="com.ejbtest.MyComponent"/>
</bean>
<bean id="myController" class="com.ejbtest.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

     其中,通過myComponent的定義創建了一個EJB代理,而這個EJB將實現特定的業務方法接口。由於EJB的本地home在應用程序啓動時就已經被緩存,所以這裏只需要一個JNDI查詢。每當這個EJB被調用時,EJB代理將調用本地EJB的classname()方法,並調用EJB提供的相應的業務方法。
 這種EJB存取機制極大地簡化了應用程序代碼,web層代碼(抑或其他EJB客戶端)將不再依賴EJB的使用。如果我們想將EJB替換爲POJO或者其他測試stub,只要把配置文件中的myComponent定義改動一下即可,Java程序依舊保持不變。而且,我們也不必再編寫任何JNDI查詢代碼,或者其他EJB的硬編碼。


      實踐表明,上述方法(包括對目標EJB的反射式調用)的運行開銷是最小的。記住,我們並不想做任何針對EJB的細粒度調用,畢竟這涉及到Java應用服務器中EJB體系架構的成本開銷。

       對於遠程EJB的訪問,本質上與訪問本地EJB是相同的,除了需要SimpleRemoteStatelessSessionProxyFactoryBean類。當然,無論是否使用Spring,遠程調用的基本語義是適用的。當訪問另外一臺電腦的虛擬機中某個對象的方法時,這種調用的具體用法和失敗處理必須以不同於本地訪問的方式處理。

      Spring在EJB客戶端支持方面,比非Spring方案具有更多優勢。通常,EJB客戶端在調用 本地或遠程EJB時,很難做自由的前後切換。 這是因爲遠程接口方法必須拋出RemoteException,並且客戶端程序必須處理此異常,但對於本地接口方法的調用則不需要這麼做。若將處理本地EJB的客戶端程序用來處理遠程EJB,那麼必須作出修改以增加對遠程異常情況的有效處理,同樣,若將處理遠程EJB的客戶端程序用來處理本地EJB,也需要做很多無謂的工作來處理遠程異常,或者將異常處理的程序代碼刪除。不過,當使用Spring遠程EJB代理時,開發人員不必在業務邏輯方法接口和具體的EJB實現代碼中聲明拋出任何RemoteException,並且將有一個統一的遠程接口(這個接口拋出RemoteException),並通過這個代理動態地分析客戶端調用的是遠程EJB還是本地EJB。 也即,客戶端程序不需要再處理RemoteException,在調用EJB過程中拋出的任何RemoteException均將被重新以RemoteAccessException類的形式拋出。於是,應用程序可以在本地EJB和遠程EJB(甚至是POJO)間任意切換調用,而客戶端代碼無需關心這些後臺的操作。當然,這個解決方案是可選的,用戶可以根據自己的意願在業務接口中聲明拋出RemoteExceptions。

2.如何利用Spring實現EJB

      Spring還提供了若干易於使用的類,來幫助開發人員實現EJB。Spring鼓勵開發人員將業務邏輯以POJO的形式實現,而將EJB的處理重點放在事務分界與遠程調用。
       無論是無狀態的會話bean,還是有狀態的會話bean,抑或消息驅動的bean,其實現均需分別繼承AbstractStatelessSessionBean, AbstractStatefulSessionBean, 和
AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean。
      考慮一個無狀態的會話bean的例子。業務接口定義如下:

public interface MyComponent {
   public void myMethod(...);
   ...
}
      我們用一個POJO實現該接口:

public class MyComponentImpl implements MyComponent {
 public String myMethod(...) {
 ...
 }
 ...
}


 最後,定義無狀態的會話bean:
 
public class MyComponentEJB extends AbstractStatelessSessionBean
implements MyComponent {

MyComponent myComp;

/**
* 從BeanFactory或者ApplicationContext獲取POJO對象
* @參見 org.springframework.ejb.support.AbstractStatelessSessionBean的onEjbCreate()方法
*/
protected void onEjbCreate() throws CreateException {
myComp = (MyComponent) getBeanFactory().getBean(
ServicesConstants.CONTEXT_MYCOMP_ID);
}

// 具體的業務方法,以POJO實現
public String myMethod(...) {
return myComp.myMethod(...);
}
...
}

 AbstractStatelessSessionBean缺省地創建並裝載一個Spring IoC容器,這個容器對EJB也是有用處的,例如獲取POJO服務對象。容器的裝載過程由 BeanFactoryLocator的一個子類完成,缺省情況下使用ContextJndiBeanFactoryLocator來實現。 ContextJndiBeanFactoryLocator從一個資源文件創建ApplicationContext,資源文件的位置由JNDI環境變量指定,例如java:comp/env/ejb/BeanFactoryPath。如果需要更改BeanFactory/ApplicationContext裝載策略,那麼缺省的BeanFactoryLocator實現可以通過調用setBeanFactoryLocator()方法來重載,這個方法可以在setSessionContext()或者EJB的構造函數中進行調用。
 有狀態的會話bean在其生命週期中存在休眠與激活的過程,若此有狀態的會話bean使用了一個非序列化的容器實例,由於該bean無法被EJB容器保存,因此它必須從 ejbPassivate()和ejbActivate()分別手工地調用unloadBeanFactory()和loadBeanFactory()方法。爲了使用EJB,ContextJndiBeanFactoryLocator類需要裝載一個ApplicationContext,這足以一般情況的需要。不過,若ApplicationContext裝載很多bean,或者這些bean的初始化可能佔用大量時間或內存資源的話,例如初始化Hibernate的一個SessionFactory對象,這個解決方案就存在較大的問題,因爲每一個EJB都將會有自己的一個copy。在此情況下,開發人員可以考慮重載缺省的ContextJndiBeanFactoryLocator,並使用BeanFactoryLocator的另一個變體,例如ContextSingletonBeanFactoryLocator類,這樣便可以在多個EJB或客戶端之間共享同一個容器。示例如下:

/**
* 重載缺省的BeanFactoryLocator實現
* @參見javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
*/
public void setSessionContext(SessionContext sessionContext) {
super.setSessionContext(sessionContext);
setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance());
setBeanFactoryLocatorKey(ServicesConstants.PRIMARY_CONTEXT_ID);
}

 而後,創建一個bean定義文件,例如beanRefContext.xml,這個文件將定義EJB可能會用到的所有bean工廠。一般情況下,這個文件將只包含一個bean的定義,例如:

<beans>
<bean id="businessBeanFactory" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg value="businessApplicationContext.xml" />
</bean>
</beans>

 其中 businessApplicationContext.xml文件包含了全部業務POJO對象的定義。ServicesConstants.PRIMARY_CONTEXT_ID則可以定義如下:

public static final String ServicesConstants.PRIMARY_CONTEXT_ID = "businessBeanFactory";


3.EJB與Spring究竟在何時進行集成? 

     儘管Spring和EJB的目標都是爲鬆散耦合的POJO提供企業級服務,但現在很多人都認爲Spring+Hibernate的方案,可以充分替代EJB,尤其EJB複雜的編程模型總是令人望而生畏,EJB的部署、運行效率亦總爲人詬病。不過,EJB尤其是EJB 3.0在複雜信息系統構建方面,依舊有着Spring目前不能超越的特性。使用EJB 3.0的時候,基於標準的方法、註釋的使用、以及與應用程序服務器的緊密集成,實現了廠商無關性,並提高開發效率。尤其是,EJB 3.0在面向事務處理的、尤其是異步通訊環境下的企業級應用方面,比Spring更具備優勢。在處理fail-over、load balancing、distributed caching和state duplication、clustering等方面,開發人員在EJB 3.0的環境中不必關心這些問題(當然,在部署時需要考慮)。而在Spring中,需要使用聲明式事務服務來管理Hibernate事務,開發人員必須在XML配置文件中顯式地配置Spring TransactionManager和Hibernate SessionFactory對象。Spring應用程序開發者必須顯式地管理跨多個HTTP請求的事務。此外,要在Spring應用程序中使用羣集服務也沒有簡單的途徑。


       EJB與Spring本質上是相互競爭且相互學習的技術,很難說二者的集成是否能夠充分彰顯二者的優點,彌補彼此的缺陷。

發佈了145 篇原創文章 · 獲贊 16 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章