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

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