David Currie 將繼續他的由三部分組成的系列文章,介紹 Java 2 企業版(J2EE)連接器架構(JCA)最新版本中的增強和變化。在本文中,他將介紹新的 JCA 1.5 工作管理合約,該合約允許資源適配器利用應用服務器的某些功能來調度和處理工作。在 JCA 的另一個增強 —— 事務流入 —— 的支持下,企業信息系統可以在自己的事務中執行這項工作。
JCA 1.5 是 J2EE 連接器體系結構的最新版本,它包含許多重要的改進和一些重要增強。本文是介紹這些變化的由三部分組成的系列文章的第 2 部分,建立在介紹的生命周期管理特性的第 1 部分的基礎上,本文介紹的是 JCA 新的工作管理合約。該合約允許資源適配器為延遲執行或定期執行的工作創建計時器,并允許計時器使用應用服務器的線程同步或異步執行處理。本文將描述事務流入支持如何在資源適配器導入到服務器的事務中進行這種處理,以及資源適配器隨后如何控制事務的完成。
如果想在現有資源適配器中使用這項功能,或者正在考慮編寫新的 JCA 1.5 資源適配器,那么本文是一份必不可少的讀物。如果要編寫使用資源適配器的應用程序,想了解更多幕后的情況,那么本文也會吸引您。
讓工作完成
在本系列的第 1 部分中,我們介紹了 ResourceAdapter
接口,它提供了一種機制,能夠在應用服務器內部為資源適配器提供生命周期。您可能還記得 start
方法被用來傳遞一個叫做 BootstrapContext
的對象。上次我們介紹了這個對象,但是 BootstrapContext
接口的三個方法才是工作管理和事務流入合約的關鍵,如清單 1 所示:
BootstrapContext
接口
|
WorkManager
允許資源適配器對工作進行調度,在應用服務器線程上同步或異步執行調度。這個工作可以在資源導入的事務中執行,在這種情況下,XATerminator
有助于完成工作。Timer
負責延遲工作或定期工作的執行。本文將更深入地研究這三個類,并說明如何使用它們。
WorkManager
接口提供了三套處理工作的方法(doWork
、 startWork
和 scheduleWork
),如清單 2 所示:
WorkManager
接口
|
每個方法接收的第一個參數,都是實現 Work
接口的對象的一個實例,如清單 3 所示:
Work
接口
|
Work
接口擴展了 Runnable
接口,您應當像直接進行 Java 線程編程時所做的那樣,實現run
方法中執行的工作。您很快就會看到 release
方法發揮其作用的地方。
WorkManager
上的 doWork
方法可以讓一些工作同步執行、一直受阻塞或者直到某些工作完成才執行。這看起來可能不是特別有用——這不就是直接調用 run
方法時發生的事情嗎?并不完全如此。首先,它讓應用程序服務器說明現在不是做這項工作的恰當時候。例如,如果在 ResourceAdapter
start
方法的范圍內調用 doWork
,那么您可能發現它將拋出 WorkRejectedException
異常。應當盡快從這個方法返回,如果可能的話,應當把工作安排成異步處理。
第二,如果應用服務器特別繁忙,那么它可能會推遲這項工作的啟動。可以用第 2 個startTimeout
參數指明資源適配器準備為工作啟動等候多長時間。如果應用服務器沒能在這個時間內啟動工作,那么就會拋出 WorkRejectedException
異常。WorkManager
接口定義了常量 IMMEDIATE
和 INDEFINITE
,它們允許資源適配器指明自己根本不準備等候或者準備一直等候下去。
第三,正如下一節解釋的,有可能讓工作片斷在資源適配器導入的事務上下文中執行,而不是在與當前線程關聯的上下文中執行。這正是第 3 個參數的用途,該參數是一個可選的 ExecutionContext
。
最后,使用 doWork
方法讓應用服務器對資源適配器執行的工作擁有更多控制。在關閉應用服務器時,如果資源適配器夾在一個漫長的、復雜的操作中間,那么服務器不需要等待資源適配器完成,或者被清理干凈。相反,服務器可以通過調用 Work
對象的 release
方法,給資源適配器發信號。然后資源適配器應當盡快完成處理。清單 4 顯示了 release
方法使用的一個示例:
Work
對象的示例
|
注意清單 4 中使用的關鍵字 volatile
。在 run
方法正進行其處理的同時,會在一個獨立的線程上調用 release
方法,volatile
修飾符確保 run
方法能夠看到更新的字段。
doWork
方法也可能失敗,拋出一個名稱古怪的 WorkCompletedException
異常。這個異常是在將 run
方法分配給一個線程時拋出的,但這個異常或者是上下文設置失敗,或者是通過拋出運行時異常退出方法。doWork
方法會提供一個錯誤代碼,表示這些故障發生的路徑,還把問題原因作為鏈接的異常提供。
為什么等待?
您已經看到 doWork
方法允許您在調用線程阻塞的同時執行工作。但是如果您不想等待 —— 也就是說,如果您不想同步執行工作,該怎么辦呢?在 Java 2 平臺標準版本(J2SE)環境中,可以使用多線程實現這一目標。但是,J2EE 規范讓應用程序服務器使用 Java 2 安全管理器防止應用程序離開自己的線程。如果應用服務器的這部分功能是通過 EJB 池或 servlet 池來提供并發性,那么這樣做是合理的。服務器也可能想把各種形式的上下文都關聯到一個線程上,因為產生新線程時,上下文可能會丟失。最后,正如我以前討論過的,不受服務器控制的線程會造成有序關機很難實現。
雖然可以通過使用安全策略,逐個案例地克服這個限制,但是 WorkManager
上的 startWork
和 scheduleWork
方法可以讓資源適配器異步地處理工作,同時確保應用程序服務器仍然在控制之下。startWork
方法會一直等候,直到工作片斷開始執行,但不用等到工作結束。所以,如果調用器需要知道工作是否已經得以執行,但是不需要等到工作完成,那么可以使用這種方法。相比之下,只要該工作調度被接受,scheduleWork
方法就立即返回。在這種情況下,并不能確保工作真的被執行。
清單 2 中的 WorkManager
方法的第 4 個參數(WorkListener
)在用于這些異步方法時最有用。清單 5 顯示了這個接口:
WorkListener
接口
|
資源適配器能夠有選擇地傳遞一個監聽器,當工作的項目通過接受、啟動或完成這幾個狀態(失敗的情況下則是拒絕)傳遞過來時,監聽器會得到通知。在這個監聽器已經完成其工作項目,或者出現故障要重新安排工作項目時,可以用它將通知發送給工作的發起者。WorkAdapter
類也包含在內,它提供了所有方法的默認實現,因此,子類只需覆蓋自己感興趣的方法即可。
WorkListener
的每個方法都采用 WorkEvent
對象作為參數,如清單 6 所示:
WorkEvent
類上的附加方法
|
除了通常的事件方法之外,WorkEvent
類還為這些類型的事件 (接受、拒絕、啟動或完成)以及有問題的工作項目提供了存取器。這使您能夠用一個(多線程的)監聽器負責多個工作提交。還有一些方法可以返回啟動工作所花費的時間,而且,在出現 workRejected
或 workCompleted
時,返回可能發生的對應的 WorkRejectedException
或 WorkCompletedException
異常。
圖 1 顯示了通過 Work
對象傳遞的狀態。
沿著圖 1 的底部,有三種提交方法,從上到下的點線表示具體的方法返回的生命周期中的時間點。
可以推遲到明天的事為什么要現在做?doWork
、startWork
和 scheduleWork
方法可以都立即向 WorkManager
提交任務。在 WorkManager
執行提交之前,可能會發生延遲,但是調用者只能控制最大延遲(用啟動超時)。那么如果想讓任務晚些而不是立即處理,該怎么辦呢?scheduleWork
方法只允許將工作安排到另一個線程,而不是安排到時間軸上的以后某個時間點上。
這正是 BootstrapContext
接口的第 3 個方法發揮其作用的地方。createTimer
方法使資源適配器能夠得到 java.util.Timer
類的實例。這個類從 1.3 版開始就已經成為標準 Java 庫的一部分,如清單 7 所示:
Timer
類的方法
|
可以用 java.util.Timer
類的前兩個方法把任務安排在指定延遲之后或指定日期和時間發生。余下的 4 個調度方法還有額外的 period
參數。可以用它們對那些初次運行之后需要按照常規間隔發生的事件進行調度,使用周期參數指定時間間隔。schedule
和 scheduleAtFixedRate
方法是不同的方法,因為這些操作的計時無法保證;像垃圾搜集這樣的操作可能會造成任務推遲執行。如果任務被延遲,那么 schedule
方法在運行下一個任務之前仍然會等候一個完整的周期。但是,scheduleAtFixedRate
方法是在前一個任務 應當 運行的固定周期之后運行下一個任務。所以,如果任務之間的時間對您非常很重要,那么請使用 schedule
。如果絕對時間或累積時間很重要,那么請使用 scheduleAtFixedRate
。
每個 schedule
方法都采用一個擴展了 TimerTask
類的對象作為自己的第一個參數。同使用 Work
接口時一樣,這個類擴展了 Runnable
,而且 Timer
會在適當的時間調用 run
方法。TimerTask
類有一個 cancel
方法,可以用它取消對任務的后續調用。或者,也可以調用 Timer
上的 cancel
方法來取消目前安排的所有任務。scheduledExecutionTime
方法允許 run
方法將當前實際調用時間和它應當被調用的時間進行比較。
雖然 TimerTask
的 run
方法是在新線程中調用 ,但是這個線程是 JVM 已經分配的線程,處于應用服務器的控制之外。如果您想讓資源適配器進行嚴肅的處理,那么在這個時候,應當用 WorkManager
切換到應用服務器的線程。清單 8 顯示的示例采用了這一良好實踐來調度工作,從當前時間開始每分鐘執行一次:
Timer
和 WorkManager
|
JCA WorkManager 和 Timer 的替代方案
正如前一小節提到過的,在 JCA 中,使用 Timer
有一個問題:執行 TimerTask
的線程不在應用服務器的控制之下。因為 Timer
是類而不是接口,而且線程實際是在類的構造函數中產生的, 所以應用服務器不能修改這種行為。允許應用服務器實現這一目的一個替代方案就是使用由 IBM 和 BEA 聯合開發的 Timer and Work Manager for Application Servers 規范的接口(請參閱 參考資料) 。
在使用這個規范的接口時,會從 Java 名稱與目錄服務接口(JNDI)得到一個 TimerManager
實現。可以通過給管理器安排了一個 TimerListener
實現來返回一個 Timer
(是一個commonj.timers.Timer
而不是 java.util.Timer
)。在安排好的時間上會調用 TimerListener
,可以用 Timer
執行諸如取消預定操作之類的操作。
顧名思義,這個規范還提供了 JCA 工作管理的一個替代。這里的接口與 JCA 的那些接口很相似,除了初始的 commmonj.work.WorkManager
是從 JNDI 得到的。這個合約提供的附加行為還包括:阻塞到一個或全部預定工作完成的能力、在遠程 JVM 上執行可以序列化的工作的可能性。與 commonj Timer
一樣,commonj WorkManager
的應用不限于資源適配器。任何服務器端 J2EE 組件都可以使用這項功能。
清單 9 顯示了為了使用 commonj 的類而被重寫的 清單 8 的示例:
清單 9. 使用 commonj 接口
|
ExampleCommonjWork
類與 清單 4 的 ExampleWork
相同,此外,它還實現了 commonj Work
接口要求的額外的 isDaemon
方法。如果工作長期存在,那么該方法應當返回 true
。
導入事務并完成事務
在 JCA 1.5 之前,應用服務器總是充當事務的協調者。應用程序負責器負責啟動事務,而且在每個資源管理器通過 JCA 連接管理器登記了自己的 XAResource
之后,還通過一起提交或回滾所有資源來協調事務的完成。JCA 1.5 的事務流入合約允許企業信息系統(EIS)啟動和完成事務,充當事務的協調者。這樣 EIS 就能通過資源適配器把事務導入應用服務器,并在事務范圍內在服務器上執行工作。例如,它可能使用 Supports
的容器管理器事務屬性來調用消息驅動 bean(MDB)。這樣,MDB 方法執行的工作,包括對其它 EJB 的調用,就會成為事務的一部分。
資源適配器通過第 3 個 ExecutionContext
參數導入事務,在 清單 2 中可以看到它被傳遞給 WorkManager
。正如在清單 10 中可以看到的,這個類擁有設置事務 ID (XID) 和事務超時的方法:
WorkManager
的 ExecutionContext
類
|
XID 惟一地標識事務,它由三部分構成:格式標識符、事務界定符和分支界定符。如果 EIS 還沒有事務的 XID,那么就必須根據 XA 規范(請參閱 參考資料)構建一個 XID。然后應用服務器會在調用工作對象的 run
方法之前把這個事務與執行線程關聯起來。
把事務導入應用服務器之后,接下來就由資源適配器負責把屬于這個事務的事件通知給服務器。具體地說,它必須把事務完成通知給應用服務器。它是通過清單 11 中的 XATerminator
接口做到這一點的,可以從 BootstrapContext
中得到它的實現:
XATerminator
類
|
XATerminator
接口的方法與 XAResource
上的方法對應,只是在這個例子中,資源適配器需要調用應用程序服務器。典型情況下,如果事務中包含不止一個資源,那么資源適配器會調用 XATerminator
的 prepare
方法,傳遞與 ExecutionContext
中傳遞的 Xid 相同的 Xid
。如果所有的資源都返回 XA_OK
(或者 XA_RDONLY
),那么資源適配器會接著調用 commit
;否則就會調用 rollback
。
結束語
本文介紹了如何用 WorkManager
接口對工作進行調度,以便在應用程序的控制下處理這些工作。您已經看到如何用 Timer
的實例在以后某個時間執行工作或定期執行工作,還了解了使用 JCA 提供的對象的 commonj 替代方案。您已經看到資源適配器如何在自己導入到應用服務器的事務中執行工作,并用 XATerminator
接口控制事務的完成。在本系列的第 3 篇也是最后一篇文章中,我將介紹 JCA 1.5 消息流入合約,它比較出名的地方是對消息驅動 bean 的支持。