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