?
? ????? PHP ??? ???? ??? ?? ??
要調(diào)用一個本地或者遠(yuǎn)程無狀態(tài)session bean上的方法,客戶端代碼必須進(jìn)行JNDI查找,以獲取(本地或遠(yuǎn)程的)EJB Home對象,然后調(diào)用該對象的"create"方法,才能得到實際的(本地或遠(yuǎn)程的)EJB對象。然后才能調(diào)用一個或者多個EJB組件的方法。
為了避免重復(fù)的底層代碼,很多EJB應(yīng)用使用了服務(wù)定位器(Service Locator)和業(yè)務(wù)委托(Bussiness Delegate)模式,這樣比在客戶端代碼中到處都進(jìn)行JNDI查找要好些,不過它們的常見實現(xiàn)都有嚴(yán)重的缺陷。例如:
通常如果代碼通過服務(wù)定位器或業(yè)務(wù)代理單件來使用EJB,則很難對其進(jìn)行測試。
如果只使用了服務(wù)定位器模式而不使用業(yè)務(wù)委托模式,應(yīng)用程序代碼仍然需要調(diào)用EJB Home組件的create方法,并且要處理由此產(chǎn)生的異常。這樣代碼依然存在和EJB API的耦合并感染了EJB編程模型的復(fù)雜性。
實現(xiàn)業(yè)務(wù)委托模式通常會導(dǎo)致大量的重復(fù)代碼,因為對于EJB組件的同一個方法,我們不得不編寫很多方法去調(diào)用它。
Spring的解決方式是允許用戶創(chuàng)建并使用代碼量很少的業(yè)務(wù)委托代理對象,通常在Spring的容器里配置。而不再需要編寫額外的服務(wù)定位器或JNDI查找的代碼,以及編碼的業(yè)務(wù)委托對象里面的冗余方法,除非它們可以帶來實質(zhì)性的好處。
假設(shè)web控制器需要使用本地EJB組件。我們將遵循最佳實踐經(jīng)驗,使用EJB的業(yè)務(wù)方法接口(Business Methods Interface)模式,這樣,這個EJB組件的本地接口將繼承一個不受EJB規(guī)范約束的業(yè)務(wù)方法接口。讓我們把這個業(yè)務(wù)方法接口稱為MyComponent。
public interface MyComponent { ... }
使用業(yè)務(wù)方法接口模式的一個主要原因是為了保證本地接口和bean的實現(xiàn)類之間的方法簽名自動同步。另外一個原因是它使得我們更容易改用基于POJO(簡單Java對象)的服務(wù)實現(xiàn)方式,只要這樣的改變是有意義的。當(dāng)然,我們也需要實現(xiàn)本地Home接口,并提供一個Bean實現(xiàn)類,用它來實現(xiàn)接口SessionBean
和業(yè)務(wù)方法接口MyComponent
?,F(xiàn)在為了把Web層的控制器和EJB的實現(xiàn)鏈接起來,我們唯一要寫的Java代碼就是在控制器上發(fā)布一個類型為MyComponent
的setter方法。這樣就可以把這個引用保存在控制器的一個實例變量中。
private MyComponent myComponent; public void setMyComponent(MyComponent myComponent) { this.myComponent = myComponent; }
然后我們可以在控制器的任意業(yè)務(wù)方法里面使用這個實例變量。假設(shè)我們現(xiàn)在從Spring容器獲得該控制器對象,我們就可以(在同一個上下文中)配置一個LocalStatelessSessionProxyFactoryBean
的實例,作為EJB組件的代理對象。這個代理對象的配置,以及控制器屬性myComponent
的設(shè)置是使用一個配置項完成的,如下所示:
<bean id="myComponent" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean"> <property name="jndiName" value="myJndiComponent"/> <property name="businessInterface" value="com.mycom.MyComponent"/> </bean> <bean id="myController" class="com.mycom.myController"> <property name="myComponent" ref="myComponent"/> </bean>
這些看似簡單的代碼背后有很多復(fù)雜的處理,受益于Spring的AOP框架,你甚至不必知道AOP概念,就可以享用它的成果。Bean myComponent
的定義創(chuàng)建了一個EJB組件的代理對象,它實現(xiàn)了業(yè)務(wù)方法接口。這個EJB組件的本地Home對象在啟動的時候就被放到了緩存中,所以只需要執(zhí)行一次JNDI查找。每當(dāng)EJB組件被調(diào)用的時候,這個代理對象就調(diào)用本地EJB組件的classname
方法,并調(diào)用EJB組件上相應(yīng)的業(yè)務(wù)方法。
The myController
bean definition sets the
myComponent
property of the controller class to the
EJB proxy.
在Bean myController
的定義中,控制器類的屬性myComponent
的值被設(shè)置為上述的EJB代理對象。
另一種選擇(在這樣的代理定義很多時,更可?。┦鞘褂迷赟pring的"jee"命名空間下的local-slsb
配置元素
<jee:local-slsb id="myComponent" jndi-name="myJndiComponent" business-interface="com.mycom.MyComponent"/> <bean id="myController" class="com.mycom.myController"> <property name="myComponent" ref="myComponent"/> </bean>
這種EJB組件訪問機制大大簡化了應(yīng)用程序代碼:Web層(或其他EJB客戶端)的代碼不再依賴于EJB組件的使用。如果我們想把這個EJB的引用替換為一個POJO,或者是模擬對象或其他測試樁架,我們只需要簡單地修改Bean myComponent
的定義而不需修改任何Java代碼,此外,我們也不必再在應(yīng)用程序中編寫任何JNDI查找或其它EJB相關(guān)的代碼。
在實際應(yīng)用中的評測和經(jīng)驗表明,這種方法(使用反射來調(diào)用目標(biāo)EJB組件)的性能開銷很小,一般使用中幾乎覺察不出。雖然如此,仍請牢記不要調(diào)用細(xì)粒度EJB組件,因為應(yīng)用服務(wù)器中EJB的基礎(chǔ)框架畢竟會帶來性能損失。
關(guān)于JNDI查找有一點需要特別注意。在Bean容器中,這個類通常最好用作單件(沒理由使之成為原型)。不過,如果這個Bean容器會預(yù)先實例化單件(XML ApplicationContext
的幾種變體就會這樣),并且它在EJB容器加載目標(biāo)EJB前被加載,我們就會遇到問題。因為JNDI查找會在該類的init方法中被執(zhí)行然后緩存結(jié)果,但是此時EJB還沒有被綁定到目標(biāo)位置。解決方案是不要預(yù)先實例化這個工廠對象,而讓它在第一次被用到的時候再創(chuàng)建。在XML容器中,這是通過屬性lazy-init
來控制的。
盡管大部分Spring的用戶不會對這些感興趣,但那些對EJB進(jìn)行AOP編程工作的用戶需要看LocalSlsbInvokerInterceptor
。
基本上訪問遠(yuǎn)程EJB與訪問本地EJB差別不大,只是前者使用的是SimpleRemoteStatelessSessionProxyFactoryBean
或者jee:remote-slsb
。當(dāng)然,無論是否使用Spring,遠(yuǎn)程調(diào)用的語義都相同;對于使用場景和錯誤處理來說,調(diào)用另一臺計算機上的虛擬機中對象的方法和本地調(diào)用會有所不同。
相比不使用Spring方式的EJB客戶端相比,Spring的EJB客戶端有多個好處。通常如果要想能隨意的在本地和遠(yuǎn)程EJB調(diào)用之間切換EJB客戶端代碼,是會產(chǎn)生問題的。這是因為遠(yuǎn)程接口的方法需要聲明他們拋出的RemoteException
方法
,然后客戶端代碼必須處理這種異常,但是本地接口的方法卻不需要這樣。如果要把針對本地EJB的代碼改為訪問遠(yuǎn)程EJB,就需要修改客戶端代碼,增加處理遠(yuǎn)程異常的代碼,反之要么保留這些用不上的遠(yuǎn)程異常處理代碼要么就需要進(jìn)行修改以去除這些異常處理代碼。使用Spring的遠(yuǎn)程EJB代理,我們就不再需要在業(yè)務(wù)方法接口和EJB的實現(xiàn)代碼中聲明要拋出的RemoteException
,而是定義一個相似的遠(yuǎn)程接口,唯一不同就是它拋出的是RemoteException
, 然后交給代理對象去動態(tài)的協(xié)調(diào)這兩個接口。也就是說,客戶端代碼不再需要與
RemoteException
這個checked exception打交道,實際上在EJB調(diào)用中被拋出的RemoteException
都將被以unchecked exception RemoteAccessException
的方式重新拋出,它是RuntimeException
的一個子類。這樣目標(biāo)服務(wù)就可以在本地EJB或遠(yuǎn)程EJB(甚至POJO)之間隨意地切換,客戶端不需要關(guān)心甚至根本不會覺察到這種切換。當(dāng)然,這些都是可選的,沒有什么阻止你在你的業(yè)務(wù)接口中聲明RemoteExceptions
異常。
通過Spring可以透明的訪問EJB2.X和EJB3的 Session bean。Spring的EJB訪問器包含jee:local-slsb
和jee:remote-slsb
,可在運行時無縫連接實際組件。
如果是EJB 2.x形式的,這些訪問器會調(diào)用home接口,如果是EJB3形式的并且沒有可用的home接口,就會直接調(diào)用組件。
注意:即使是EJB3 session bean,你也完全可以使用JndiObjectFactoryBean
/ jee:jndi-lookup
因為很多可用的組件引用很多是暴露為JNDI查找的。顯式的定義jee:local-slsb
/ jee:remote-slsb
查找提供了一致性和更清楚的EJB訪問配置。