引言
Enterprise Java Beans(簡(jiǎn)稱EJB)是Java Enterprise Edition(簡(jiǎn)稱Java EE)平臺(tái)上的服務(wù)端組件架構(gòu)模型,目標(biāo)極力于快速并簡(jiǎn)化分布式,事務(wù)處理,安全以及便攜式的應(yīng)用程序。
EJB在其2.*時(shí)代也叱詫風(fēng)云過(guò),由于能夠解決許多企業(yè)應(yīng)用程序的需求而被廣泛采納。但這只是EJB成功的表象,越來(lái)越多的質(zhì)疑聲開(kāi)始抨擊EJB的復(fù)雜。“缺乏好的持久層策略,又臭又長(zhǎng)的布署描述符,能力有限的單元測(cè)試”等等這些常用卻又不好用的技術(shù)導(dǎo)致了大量開(kāi)發(fā)人員開(kāi)始尋找新的“輪子”。
Sun的反應(yīng)的確有些遲鈍,但還是它花費(fèi)大量精力來(lái)修訂規(guī)范,使得EJB得到很大的改觀。EJB3擯棄了許多現(xiàn)有的缺點(diǎn),呈現(xiàn)給開(kāi)發(fā)人員的解決方案在社區(qū)中大受好評(píng)。EJB又一次變成了切實(shí)可行的解決方案,并且現(xiàn)在已經(jīng)有許多放棄它的團(tuán)隊(duì),再次接收EJB。
雖然它成功了,但EJB3還沒(méi)有當(dāng)初預(yù)計(jì)的那么理想。反觀EJB2.1,新規(guī)范要面對(duì)兩個(gè)主要的挑戰(zhàn):
1. 為了改變EJB2.1現(xiàn)有的特性, ( 比如說(shuō)需要強(qiáng)大的持久層框架來(lái)代替 Entity Beans ;支持使用 Annotation 來(lái)代替布署描述符;拋棄 home interface 等等。 ) ,需要進(jìn)行大量的重建工作。
2. 為了引入新的解決方案,需要加入原先規(guī)范中沒(méi)有的新技術(shù)。( 比如說(shuō)支持 Singletons ;支持方法攔截;支持異步調(diào)用;改進(jìn)并增強(qiáng)現(xiàn)有的 Timer Service 特性 )
對(duì)于EJB,優(yōu)先考慮的就是全部重建。“我們只有先清空杯子里的水,才能接納新的東西”。現(xiàn)在杯子已經(jīng)清空了,這對(duì)我們來(lái)說(shuō)是非常有利的,而且還可以沒(méi)有包袱的大膽前進(jìn)。
EJB3.1 又一次引入了一系列新的特性,傾向于挖掘技術(shù)的潛力。依我來(lái)看,EJB3.1絕對(duì)是一個(gè)重要的發(fā)布版本,它將那些長(zhǎng)期讓人渴望的特性帶到開(kāi)發(fā)者面前,更加能夠滿足最新的企業(yè)應(yīng)用程序開(kāi)發(fā),同時(shí)對(duì)EJB再次被人們采納將做出巨大的貢獻(xiàn)。
近期,EJB 3.1提議最終草案已經(jīng)發(fā)布了,現(xiàn)在我們已經(jīng)非常接近最終發(fā)行版了。本文會(huì)貫穿大多數(shù)新的特性,對(duì)每一個(gè)新特性都有會(huì)有一定程度的介紹。
No-Interface View(非接口視圖)
EJB3.1引入了no-interface view的概念——將一個(gè)bean的所有的public method通過(guò)一個(gè)Local View暴露出來(lái)。 ( 具體訪問(wèn)可見(jiàn)參見(jiàn)本文后面的 Global JNDI names ) Session Beans并不強(qiáng)迫你再去實(shí)現(xiàn)任何接口。EJB容器提供一個(gè)指向no-interface view的引用實(shí)現(xiàn),允許客戶端調(diào)用該bean的任何public method,并且no-interface view也可以確保事務(wù),安全以及攔截的行為與原先的用法一致。
通過(guò)non-interface view,所有bean的public method(當(dāng)然也包括定義在其父類上的public method)都是可用的。一個(gè)客戶端可以通過(guò)依賴注入或JNDI lookup得到該view的引用,用起來(lái)感覺(jué)就好像它是local或remote的view一樣。
但與local和remote的view不同的是, local 和 remote 的 view 必須與其所實(shí)現(xiàn)的的業(yè)務(wù)接口同時(shí)存在 ,而no-interface view的引用則只是bean這個(gè)類本身。注意, no-interface view 不再依賴于接口 。
下面的代碼樣例說(shuō)明了一個(gè)servlet使用no-interface view是一件多么容易事啊。這個(gè)被引用的no-interface view叫作ByeEJB,其實(shí)就是一個(gè)普普通通的JavaBean。該EJB并未實(shí)現(xiàn)任何接口,也沒(méi)有提供什么多余的布署描述符。最后但并不是最不重要的一點(diǎn)就是,這里EJB引用的獲得使用了依賴注入進(jìn)行簡(jiǎn)化。
- ByeServlet
- (...)
- @EJB
- privateByeEJBbyeEJB;
- publicStringsayBye(){
- StringBuildersb=newStringBuilder();
- sb.append("<html><head>");
- sb.append("<title>ByeBye</title>");
- sb.append("</head><body>");
- sb.append("<h1>"+byeEJB.sayBye()+"</h1>");
- sb.append("</body></html>");
- returnsb.toString();
- }
- (...)
- ByeEJB
- @Stateless
- publicclassByeEJB{
- publicStringsayBye(){
- return"Bye!";
- }
- }
實(shí)際上如果引用類型為java類而不是接口的話,還是有些硬性的限制條件:
l 客戶端永遠(yuǎn)無(wú)法使用new操作符來(lái)獲得引用。 ( 很明顯如果是你自己 new 出來(lái)的, EJB 容器自然無(wú)法托管 )
l 除了public method外,如果其它方法如果出錯(cuò),也會(huì)拋出EJBException異常。
l 一個(gè)指向該view的引用可以作為任何本地接口或其它no-interface view方法的參數(shù)進(jìn)行傳遞或返回。
如果bean沒(méi)有暴露任何local或remote view,則容器必須默認(rèn)提供一個(gè)可用的no-interface view。如果bean提供了至少一個(gè)local或remote view,則窗口不會(huì)提供no-interface view(除非使用@LocalBean顯式要求提供).
Bean和其父類的所有public method都會(huì)通過(guò)no-interface view被暴露出來(lái)。這也意味著任何public callback method也會(huì)暴露出來(lái),因此在使用的時(shí)候要注意這一點(diǎn)。
本特性避免了接口的編寫,簡(jiǎn)化了程序的開(kāi)發(fā)(實(shí)際上并不是所有的類都需要接口)。也許在不久的將來(lái)還會(huì)加入remote no-interface view。
Singleton
大多數(shù)的應(yīng)用程序都有過(guò)至少需要一個(gè)singleton bean ( 對(duì)每個(gè)應(yīng)用程序來(lái)說(shuō),它意味著只需要初始化一次 ) 的經(jīng)歷。許多供應(yīng)商已經(jīng)滿足了這方面的需求,通過(guò)使用描述布署符來(lái)限定一個(gè)bean所允許的最大實(shí)例數(shù)量。供應(yīng)商這種“各自為政”的方式破壞了JAVA到處宣揚(yáng)的“一次編寫,到處布署”的口號(hào),因此迫切的再推出一套類似特性的規(guī)范來(lái)很有必要。EJB 3.1最終還是引入了singleton session beans。
現(xiàn)在主流的session beans有三類——stateless,stateful 和 singleton。Singleton session beans可通過(guò)使用Singleton Annotation來(lái)標(biāo)注,然后每個(gè)應(yīng)用程序會(huì)確保只實(shí)例化一次。Singleton session beans支持與客戶端共享,當(dāng)然也支持并發(fā)訪問(wèn) ( 后面有會(huì)具體談到 ) 。
singleton bean的生命周期始于容器下列任意初始化階段:
1. 直接實(shí)例化某個(gè)singleton
2. 通過(guò)依賴注入實(shí)例化某個(gè)singleton,這樣其依賴的那些singleton也會(huì)被跟著實(shí)例化,如此遞推下去。
3. 通過(guò)執(zhí)行PostConstruct回調(diào)
缺省情況下,容器有義務(wù)決定singleton bean何時(shí)被創(chuàng)建 ( 比如在 spring 中,默認(rèn)是將所有的 singleton 在啟動(dòng)時(shí)就初始化 ) ,但是也允許開(kāi)發(fā)人員使用Startup annotation在應(yīng)用程序啟動(dòng)時(shí),要求容器去對(duì)singleton進(jìn)行初始化。此外,Startup annotation還允許你去定義singleton beans之間的依賴關(guān)系。當(dāng)容器開(kāi)始處理任何客戶端發(fā)過(guò)來(lái)的請(qǐng)示時(shí),所有標(biāo)注有startup的singletons都必須初始化完成。
下面的代碼樣例大致演示了依賴是如何實(shí)現(xiàn)的。singleton A的沒(méi)有使用@Startup也沒(méi)有別的singleton依賴于它,于是A的實(shí)例化由容器來(lái)決定 ( 說(shuō)白了,此時(shí) A 的實(shí)例化由具體 EJB 實(shí)現(xiàn)來(lái)決定,規(guī)范沒(méi)有硬性規(guī)定 ) 。singleton B在應(yīng)用程序啟動(dòng)過(guò)程中被實(shí)例化,但必須早于singleton D和singleton E ( 很明顯,沒(méi)有 B 的實(shí)例, C 和 D 可能都無(wú)法正常初始化 ) 。即使這個(gè)時(shí)候的B沒(méi)有使用@Startup,但由于有其它的singletons依賴于它,它還是要先實(shí)例化的。singleton C由于使用了@Startup,它會(huì)在應(yīng)用程序啟動(dòng)過(guò)程中被實(shí)例化,但必須比singleton E先完成。同理D也必須在E之前完成實(shí)例化。因此,E是應(yīng)用程序中會(huì)最后一個(gè)被初始化。 ( 注意,再重申一下 A 是否會(huì)初始化與供應(yīng)商的實(shí)現(xiàn)有關(guān),也許供應(yīng)商的實(shí)現(xiàn)是預(yù)先加載,也許是延遲加載,但當(dāng)你真正使用 A 的時(shí)候,肯定會(huì)保證被初始化了 。 )
- @Singleton
- publicclassA{(...)}
- @Singleton
- publicclassB{(...)}
- @Startup
- publicclassC{(...)}
- @Startup(DependsOn="B")
- @Singleton
- publicclassD{(...)}
- @Startup(DependsOn=({"C","D"})
- @Singleton
- publicclassE{(...)}
有一點(diǎn)需要注意的是,如果一個(gè)bean依賴于多個(gè)bean的注入,那么這些被依賴的beans之間的初始化時(shí)機(jī)是不確定的。E依賴于C和D,并沒(méi)有說(shuō)C一定要在D之前被實(shí)例化,除非D本身也是依賴于C的。
singleton可以@Startup定義依賴于現(xiàn)存其它模塊中的singletons。
當(dāng)應(yīng)用程序關(guān)閉時(shí),容器有義務(wù)執(zhí)行singletons的PreDestory回調(diào),將所有的singletons全部銷毀。這個(gè)時(shí)候,啟動(dòng)時(shí)候的依賴關(guān)系在銷毀時(shí)變得有意義了,比如說(shuō)A依賴于B,當(dāng)A被銷毀時(shí),B還是存活的,剛好與初始化相反。
Singleton bean會(huì)維護(hù)服務(wù)端與客戶端調(diào)用而產(chǎn)生的狀態(tài),但當(dāng)應(yīng)用程序關(guān)閉或容器掛掉時(shí),該狀態(tài)并不會(huì)保存下來(lái)。 ( 大家一定想到這種情況如果使用序列化機(jī)制將狀態(tài)保存下來(lái),然后當(dāng)程序再次啟動(dòng)時(shí),再反序列化還原狀態(tài)也是一種選擇。不過(guò),至于你 singleton 中裝的是什么內(nèi)容,是否需要被序列化,是否可以被序列化對(duì)容器來(lái)說(shuō)還是未知數(shù),所以干脆掛就掛吧。 ) 為了處理服務(wù)端與客戶端的并發(fā)調(diào)用問(wèn)題,開(kāi)發(fā)人員必須定義一個(gè)并發(fā)策略。規(guī)范中定義了兩種方式:
l CMC(Container-managed concurrency 容器托管的并發(fā)機(jī)制):顧名思義,由容器來(lái)管理該bean實(shí)例的并發(fā)調(diào)用。這也是EJB的默認(rèn)策略。
l BMC(Bean-managed concurrency Bean托管的并發(fā)機(jī)制):容器此時(shí)并不會(huì)干涉該bean實(shí)例的并發(fā),把并發(fā)的同步調(diào)用推回給開(kāi)發(fā)人員。BMC允許使用合法的同步原語(yǔ)(如synchronized 和volatile關(guān)鍵字),來(lái)協(xié)調(diào)不同客戶端不同線程對(duì)同一個(gè)singleton的并發(fā)訪問(wèn)。
( 呵呵,這不正是聲明式與編程式的又一次實(shí)踐嗎 ? )
大多數(shù)情況下,CMC肯定是首選。容器的管理并發(fā)問(wèn)題時(shí),還是使用“Lock (鎖)”。每個(gè)方法都關(guān)聯(lián)上一個(gè)read lock和write lock。Read lock表示應(yīng)該方法可以盡可能的被多個(gè)線程并發(fā)調(diào)用,而write lock表示該方法在每次只能每一個(gè)線程訪問(wèn)。
缺省情況下,lock的屬性值是write。當(dāng)然你可以通過(guò)使用@Lock來(lái)修改默認(rèn)屬性值。@Lock可以用于類,接口和方法。如其它Annotation類似,@Lock也有繼承性。在類級(jí)別使用@Lock,它的所有相關(guān)方法也會(huì)被應(yīng)用上,除非你單獨(dú)限制某一個(gè)具體的方法。
當(dāng)某個(gè)方法持有 write lock時(shí),容器只允許其中一個(gè)并發(fā)線程去調(diào)用該方法。其它線程并必須等待,直到該方法再次變得可用。客戶端的等待也許是無(wú)限期的,這個(gè)時(shí)候可以使用@ AccessTimeout來(lái)指定一個(gè)最大等待時(shí)間。如果超時(shí)了,會(huì)拋出ConcurrentAccessTimeoutException異常。
下面的代碼示例中演示了如果使用CMC。Singleton A明確指定為CMC( 盡管這么沒(méi)有必要,因?yàn)槟J(rèn)就是 CMC ,主要還是為了演示 )。Singleton B并未定義任何并發(fā)策略,但按照規(guī)范,它還是屬性CMC范疇,它的所有方法顯示指定CMC使用 write lock方式。Singleton C與Singleton B幾乎一模一樣,只是使用的是read lock方式。Singleton D和Singleton C一樣,但是D中的sayBye方法重新定義為 write lock。Singleton E總要是演示@AccessTimeout的使用,當(dāng)有因等待E中某方法而被阻塞的客戶端超過(guò)10秒時(shí),就會(huì)招出ConcurrentAccessTimeoutException異常。
- @Singleton
- @ConcurrencyManagement(CONTAINER)
- publicclassA{(...)}
- @Singleton
- @Lock(WRITE)
- publicclassB{(...)}
- @Singleton
- @Lock(READ)
- publicclassC{(...)}
- @Singleton
- @Lock(READ)
- publicclassD{
- (...)
- @Lock(WRITE)
- publicStringsayBye(){(...)}
- (...)
- }
- @Singleton
- @AccessTimeout(10000)
- publicclassE{(...)}
如果是在集群環(huán)境下,當(dāng)應(yīng)用程序布署在不同的JVM上,則每個(gè)JVM都有該singleton的一個(gè)實(shí)例。
直到EJB 3,任何由EJB拋出系統(tǒng)異常都會(huì)導(dǎo)致實(shí)例被廢棄和銷毀。但這個(gè)原則并不適用于singleton beans——它們必須一直存活下來(lái),至少應(yīng)用程關(guān)閉時(shí)才銷毀。因此任何在業(yè)務(wù)對(duì)像方法或回調(diào)拋出系統(tǒng)異常時(shí),業(yè)務(wù)對(duì)象并不會(huì)被銷毀。
與 stateless beans一樣,singletons也可以暴露成web services。
Asynchronous Invocations( 異步調(diào)用 )
Session beans方法的異步調(diào)用是這些新特性中最重要的特性之一。它可以應(yīng)用于所有類型的session beans。規(guī)范規(guī)定:在容器開(kāi)始執(zhí)行某個(gè)bean實(shí)例的調(diào)用之前,異步調(diào)用的控制權(quán)一定要返回給客戶端。這又將session beans提高到了一個(gè)嶄新的高度——使有潛在異步調(diào)用需求開(kāi)發(fā)人員從session beans中獲得更多好處,也允許客戶端觸發(fā)并行處理的流程。
通過(guò)使用@Asynchronous就可以將一個(gè)類或方法標(biāo)記為異步調(diào)用。下面的示例演示了該annotation不同場(chǎng)合下的應(yīng)用。Bean A將其所有方法標(biāo)注為異步;SingletonB中,只有定義的flushBye方法才是異步的;對(duì)stateless C而言,所有的通過(guò)local interface Clocal接口調(diào)用的方法都是異步的;而通過(guò)Cremote接口調(diào)用卻是同步的。因此,同一個(gè)方法還可能由于所引用的不同接口而表現(xiàn)出不同的行為。最后,bean D的flushBye肯定是異步的,于Dlocal是否是不是異步已經(jīng)無(wú)關(guān)了。
- @Stateless
- @Asynchronous
- publicclassA{(...)}
- @Singleton
- publicclassB{
- (...)
- @Asynchronous
- publicvoidflushBye(){(...)}
- (...)
- }
- @Stateless
- publicclassCimplementsCLocal,CRemote{
- publicvoidflushBye(){(...)}
- }
- @Local
- @Asynchronous
- publicinterfaceCLocal{
- publicvoidflushBye();
- }
- @Remote
- publicinterfaceCRemote{
- publicvoidflushBye();
- }
- @Stateless
- publicclassDimplementsDLocal{(...)}
- @Local
- publicinterfaceDLocal{
- (...)
- @Asynchronous
- publicvoidflushBye();
- (...)
- }
注意異步方法調(diào)用的返回類型必須是void或Future<V>(其中V表示返回值的類型)。如果方法的返回值為void,則不允許聲明任何應(yīng)用程序異常。
Future接口在Java 5 中就被引入,提供四個(gè)主要方法:
- cancel(booleanmayInterruptIfRunning):嘗試取消異步方法的執(zhí)行。如果某個(gè)bean的實(shí)例方法還未開(kāi)始調(diào)用,容器會(huì)嘗試取消這個(gè)調(diào)用。 如果為參數(shù)為 true:執(zhí)行中的任務(wù)可以被interrupt;如果參數(shù)為false:允許這個(gè)任務(wù)執(zhí)行完畢 。標(biāo)志位“mayInterruptIfRunning”用于控制目標(biāo)bean是否對(duì)客戶端是否可見(jiàn),免得該異步調(diào)用被客戶端不小心給取消了。
- get:當(dāng)方法調(diào)用完成時(shí),返回結(jié)果。該get方法有兩個(gè)重載版本,一個(gè)是調(diào)用后一直處于阻塞狀態(tài),直到方法調(diào)用完成;另一個(gè)則可以設(shè)置一個(gè)超時(shí)的參數(shù)。
- isCancelled:指示該方法是否被取消。
- isDone:指示該方法是否執(zhí)行完成。
規(guī)范要求容器提供AsyncResult<V>類作為Future<V>接口的實(shí)現(xiàn),它可以將執(zhí)行后的返回結(jié)構(gòu)作為構(gòu)造函數(shù)的參數(shù),請(qǐng)看下面代碼:
- @Asynchronous
- publicFuture<String>sayBye(){
- Stringbye=executeLongQuery();
- returnnewAsyncResult<String>(bye);
- }
Future<V>的返回類型因不同的客戶端角度而異。因此,如果在一個(gè)標(biāo)有@Asynchronous
接口中定義了方法m,那么請(qǐng)注意,只有這個(gè)接口中的方法允許返回類型為Future<V>,在其它別的非異步接口中的定義中不能帶有Future<V>,只能返回普通的V( 注意 V 在這里表示的是泛型 )。請(qǐng)看下面示例:
- @Stateless
- publicclassByeEJBimplementsByeLocal,ByeRemote{
- publicStringsayBye(){(...)}
- }
- @Local
- @Asynchronous
- publicinterfacesayBye{
- publicFuture<String>flushBye();
- }
- @Remote
- publicinterfaceByeRemote{
- publicStringsayBye();
- }
SessionContext接口有一個(gè)wasCancelCalled方法,用于判斷客戶端是否調(diào)用了Future和cancel方法。如果Future的cancel方法的mayInterruptIfRunning參數(shù)設(shè)置為true,那么wasCancelCalled自然也會(huì)返回true,也就是說(shuō)異步調(diào)用被終止了。( 也就是說(shuō) SessionContext 可以用于判斷某個(gè)異步方法是否被取消,提高程序的健壯性 )。請(qǐng)看代碼示例:
- @Resource
- SessionContextctx;
- @Asynchronous
- publicFuture<String>sayBye(){
- Stringbye=executeFirstLongQuery();
- if(!ctx.wasCancelCalled()){
- bye+=executeSecondLongQuery();
- }
- returnnewAsyncResult<String>(bye);
- }
( 下面代碼似乎有筆誤,根本無(wú)法通過(guò)編譯 )
如果異步方法拋出了一個(gè)普通應(yīng)用程序異常,則這個(gè)異常傳播到客戶端時(shí)必須為ExecutionException。原始的異常信息仍然可以通過(guò)調(diào)用getCause來(lái)獲得的。
- @Stateless
- publicclassByeEJBimplementsByeLocal{
- publicStringsayBye()throwsMyException{
- thrownewMyException();
- }
- }
- @Local
- @Asynchronous
- publicinterfaceByeLocal{
- publicFuture<String>sayBye();
- }
- //Client
- @Stateless
- publicclassClientEJB{
- @EJB
- ByeLocalbyeEjb;
- publicvoidinvokeSayBye(){
- try{
- Future<String>futStr=byeEjb.sayBye();
- }catch(ExecutionExceptionee){
- StringoriginalMsg=ee.getCause().getMessage();
- System.out.println("Originalerrormessage:"+originalMsg);
- }
- }
- }
對(duì)于異步方法的執(zhí)行,客戶端的事務(wù)上下文并不會(huì)傳播到。因此,當(dāng)下列異步事務(wù)方法調(diào)用時(shí),可以得到的結(jié)論分別為:
- 如果方法m的事務(wù)屬性定義為“REQUIRED”,那么它的表現(xiàn)形式將永遠(yuǎn)為“REQUIRES_NEW”。
- 如果方法m的事務(wù)屬性定義為“MANDATORY”,那么它的表現(xiàn)形式永遠(yuǎn)是拋出TransactionRequiredException異常。
- 如果方法m的事務(wù)屬性定義為“SUPPORTS”,那么它的表現(xiàn)形式永遠(yuǎn)是不會(huì)參與在事務(wù)上下文中。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
