在很多企業(yè)應(yīng)用中有時需要在特定的時間運(yùn)行一段代碼,比如銀行需要在晚上系統(tǒng)相對空閑的時間內(nèi)進(jìn)行日結(jié)的對帳,那么到了規(guī)定時間系統(tǒng)需要觸發(fā)對帳服 務(wù)運(yùn)行對帳程序,現(xiàn)在EJB定時器服務(wù)能解決這個問題,它是一個可靠的、事務(wù)性的、提供容器的服務(wù),允許Bean提供者注冊定時反饋的企業(yè)Beans,它 可以在特定時刻發(fā)生,或在某段時間之后發(fā)生,或以一定時間間隔重復(fù)發(fā)生。由于這個服務(wù)是可靠的,容器破壞的時候定時依然有效,企業(yè)Beans的激活與失 效、裝載與保存周期都由定時器注冊。定時器服務(wù)由EJB容器實(shí)現(xiàn),定時器服務(wù)可以通過EJBContext接口新增的getTimerService() 方法來訪問,它返回實(shí)現(xiàn)TimerService接口的對象:這個接口允許創(chuàng)建不同的定時器來支持在不同時間、不同時間間隔、不同時間周期時發(fā)生的定時反 饋。使用定時器服務(wù)的企業(yè)Beans的Bean類必須實(shí)現(xiàn)javax.ejb.TimedObject接口。在EJB2.1中,只有無序的會話Beans 和實(shí)體Beans可以注冊為定時器服務(wù)。這個功能在以后的規(guī)范中可能會擴(kuò)展到其它類型的Bean。
定時器服務(wù)適合長時間的業(yè)務(wù)處理模型,但并不適合用于實(shí)時的事件模型。
在 WebSphere Application Server 6中,EJB 定時服務(wù)將 EJB 計(jì)時器作為新的調(diào)度程序服務(wù)任務(wù)實(shí)施。缺省情況下,內(nèi)部調(diào)度程序?qū)嵗糜诠芾砟切┤蝿?wù),定時任務(wù)存放在與服務(wù)器進(jìn)程關(guān)聯(lián)的 Cloudscape 數(shù)據(jù)庫中。在集群環(huán)境中,任務(wù)必須存放在企業(yè)關(guān)系型數(shù)據(jù)庫中。下面我們以DB2為例講述怎樣在集群環(huán)境中配置定時服務(wù)。
![]() ![]() |
![]() |
2、 創(chuàng)建用于定時服務(wù)的數(shù)據(jù)庫
每個定時服務(wù)程序都需要一個數(shù)據(jù)庫,以用于存儲它的持久信息。數(shù)據(jù)庫及其位置應(yīng)當(dāng)由應(yīng)用程序開發(fā)者和服務(wù)器管理員決定。 定時服務(wù)程序使用這個數(shù)據(jù)庫來存儲任務(wù),然后運(yùn)行這些任務(wù)。定時服務(wù)程序性能極大地依賴于數(shù)據(jù)庫的性能。如果需要每秒執(zhí)行更多任務(wù),您可以在更大型的系統(tǒng) 中運(yùn)行定時服務(wù)程序守護(hù)程序,或通過使用多個定時服務(wù)程序?qū)θ蝿?wù)或分區(qū)使用的會話 bean 使用集群。但是,定時服務(wù)程序數(shù)據(jù)庫最終會達(dá)到飽和狀態(tài),此時您就需要一個更大型或更優(yōu)異的數(shù)據(jù)庫系統(tǒng)。要獲取有關(guān)調(diào)度程序拓?fù)涞脑敿?xì)信息,請參閱技術(shù)資 料"WebSphere Enterprise Scheduler planning and administration guide"。
當(dāng)您在每個定時服務(wù)程序配置中指定唯一的表前綴值時,多個定時服務(wù)程序可以共享一個數(shù)據(jù)庫。這一共享可以降低定時服務(wù)程序數(shù)據(jù)庫的管理成本。
TIPS:Oracle XA 數(shù)據(jù)庫的限制,Oracle XA 不允許在全局事務(wù)環(huán)境中執(zhí)行所需的模式操作。本地事務(wù)是不受支持的。如果您的調(diào)度程序使用 Oracle XA 數(shù)據(jù)源,您可以將調(diào)度程序配置臨時更改為使用一個非 XA Oracle 數(shù)據(jù)源,或者使用提供的 DDL 文件手工創(chuàng)建表。如果使用管理控制臺為配置為使用 Oracle XA 數(shù)據(jù)源的調(diào)度程序創(chuàng)建或刪除調(diào)度程序表,您將接收到一條 SchedulerDataStoreException 錯誤消息并且操作將失敗。
下面我們將以DB2為例講述定時服務(wù):
在機(jī)器hostdb上安裝DB2后,打開 DB2 命令行窗口。
確保您擁有數(shù)據(jù)庫系統(tǒng)的管理員權(quán)限,驗(yàn)證此數(shù)據(jù)庫確實(shí)支持 Unicode(UTF-8)。 否則,此數(shù)據(jù)庫無法存儲 Java 代碼中可以處理的所有字符,當(dāng)客戶機(jī)使用了不兼容的代碼頁時,這將導(dǎo)致代碼頁轉(zhuǎn)換問題。要避免死鎖,請確保將 DB2 隔離級別設(shè)置為"讀穩(wěn)定性"。如果需要,請輸入命令 :
db2set DB2_RR_TO_RS=YES |
然后重新啟動 DB2 實(shí)例以激活這一更改。在 DB2 命令行處理程序中輸入以下命令使用示例名 timerdb 創(chuàng)建數(shù)據(jù)庫:
db2 CREATE DATABASE scheddb USING CODESET UTF-8 TERRITORY en-us |
即可創(chuàng)建名為 timerdb 的 DB2 數(shù)據(jù)庫。
現(xiàn)已為定時服務(wù)創(chuàng)建了 DB2 數(shù)據(jù)庫。
![]() ![]() |
![]() |
在WAS6的安裝目錄下,有一個名為scheduler的目錄。下面包含WAS容器用來管理定時服務(wù)的各種數(shù)據(jù)庫SQL定義。 對應(yīng)于DB2的SQL定義文件名為createSchemaDB2.ddl和createTablespaceDB2.ddl,修改這兩個文件選擇你所要新建的表空間名和你所要的模式名稱。這兩個文件大致內(nèi)容如下:
createTablespaceDB2.ddl |
可以修改表空間名稱,這個文件也可不做修改。然后修改createSchemaDB2.ddl 修改后的的結(jié)果可以去掉原來的模式名稱,那么新建用戶表的時候?qū)⑹褂萌笔∵B接數(shù)據(jù)庫的用戶的模式名。
CREATE TABLE "TASK" ("TASKID" BIGINT NOT NULL , |
這兩個文件修改完成后,在命令行運(yùn)行db2cmd轉(zhuǎn)到db2命令窗口。
然后運(yùn)行db2batch -d timerdb -f createTablespaceDB2.ddl
和db2batch -d timerdb -f createSchemaDB2.ddl生成定時服務(wù)所需要的表空間和表。
運(yùn)行完成后用下列命令驗(yàn)證:
Db2 connect to timerdb |
你將會看到有以下四個表被創(chuàng)建:
Table/View Schema Type |
其中主表task存放了定時程序的相關(guān)信息。
![]() ![]() |
![]() |
分別在hosta,hostb,hostc上完成WAS6安裝后,我們需要創(chuàng)建3個節(jié)點(diǎn)來組成一個新的群集。
1)在hosta上創(chuàng)建一個Network DeployManagement節(jié)點(diǎn),啟動概要表創(chuàng)建向?qū)В?/p>
選擇創(chuàng)建Deployment Manager概要表:

點(diǎn)下一步直至完成
。2)分別在hostc和hostb兩個節(jié)點(diǎn)上選擇創(chuàng)建應(yīng)用服務(wù)器概要表。
3)創(chuàng)建完成后在DeployManager概要上運(yùn)行startManager.sh啟動Network Manager。
4)啟動完成后打開概要下的日志文件SystemOut.log查看soap端口,缺省為8879。
5)在hostb和hostc兩個應(yīng)用服務(wù)器節(jié)點(diǎn)上運(yùn)行addNode.sh hosta 8879
6)運(yùn)行完成后,打開ND管理控制臺:http://hosta:9060/ibm/console
7)在服務(wù)器下新建一個群集timertest,創(chuàng)建兩個成員為clus01,clus02。啟動群集。
![]() ![]() |
![]() |
5、 創(chuàng)建定時服務(wù)的數(shù)據(jù)源
進(jìn)入ND管理控制臺,展開資源,點(diǎn)擊JDBC 提供者,選擇要新建的資源所在的服務(wù)器

點(diǎn)新建。按提示輸入所需資料。點(diǎn)數(shù)據(jù)源,進(jìn)入數(shù)據(jù)源頁面。
新建一個名為testtimer的數(shù)據(jù)源,指定jndi名為jdbc/testtimer

測試連接通過后。做下一步設(shè)置。
![]() ![]() |
![]() |
打開管理控制臺。
單擊服務(wù)器 >應(yīng)用程序服務(wù)器 > 服務(wù)器名 > EJB 容器設(shè)置 > EJB 定時服務(wù)設(shè)置。 出現(xiàn)"定時服務(wù)設(shè)置"面板。
如果您要使用內(nèi)部或預(yù)配置的調(diào)度程序?qū)嵗瑒t單擊使用內(nèi)部 EJB 定時服務(wù)調(diào)度程序?qū)嵗龁芜x按鈕。
如果您選擇不更改缺省的設(shè)置,則此實(shí)例與 Cloudscape 數(shù)據(jù)庫相關(guān)聯(lián)。
更改數(shù)據(jù)源選擇輸入您所選的數(shù)據(jù)源別名。選擇前面創(chuàng)建的jdbc/testtimer數(shù)據(jù)源。
輸入表前綴為你創(chuàng)建表時的用戶缺省模式名稱,必須注意的是,在模式名稱后面必須要帶上一個小數(shù)點(diǎn).。具體對應(yīng)的每個值的意思可以點(diǎn)擊幫助頁面查看。
![]() ![]() |
![]() |
7、 開發(fā)基于J2EE標(biāo)準(zhǔn)的定時服務(wù)企業(yè)bean
下面的例子是在RAD環(huán)境下開發(fā),要實(shí)現(xiàn)定時服務(wù),EJB必須要實(shí)現(xiàn)javax.ejb.TimedObject接口。
EJB里面需要涉及到的接口或類分別是:
要實(shí)現(xiàn)的TimedObject和TimerService,Timer。
在EJB中的ejbContext增加了一個方法.getTimerService();用于獲得TimerService類。但這個方法不能在 setEntityContext()、setSessionContext()、setMessageContext()方法中調(diào)用。
下面列出了三個接口的定義:
public interface TimedObject{ |
對于Stateless SessionBean來說,不要在ejbCreate()和ejbRemove()中設(shè)置Timer。主要是因?yàn)閑jbCreate和ejbRemove調(diào)用的時間和次數(shù)都因Container Vendor而異。可能導(dǎo)致錯誤設(shè)置Timer。
對MessageDriven Bean 而言,和Stateless SessionBean的情況基本相似。但是設(shè)置Timer應(yīng)該在onMessage()里面。通過一個JMS來進(jìn)行觸發(fā)。
ejbTimedout是一個回調(diào)方法,執(zhí)行具體的商業(yè)邏輯,那么怎樣設(shè)置什么時間觸發(fā)這個方法呢,我們利用javax.ejb.TimerSevice。該對象我們可以從EJBContext中獲得該對象實(shí)例。
當(dāng) 定時器創(chuàng)建的特定時間到達(dá)后,容器就會觸發(fā)ejbTimeout(),運(yùn)行ejbTimeOut方法提,Bean在終止之前通過調(diào)用定時器Cancel方 法取消定時器,它是定時器接口的一部分,如果定時器被取消,ejbTimeout()方法就不會被調(diào)用了。定時器接口的getHandle()方法返回一 個序列化的handle對象。接下來,這個持續(xù)的Handle能夠"非序列化",通過調(diào)用getTimer()方法得到定時器。由于定時器是本地對象, TimerHandle不必通過Bean的遠(yuǎn)程接口或Web Services接口來傳遞。
具體步驟如下:
新建一個EJB項(xiàng)目otherTimer,在這個EJB項(xiàng)目里新建一個otherTimer的會話Bean。
在Bean實(shí)體里面需要實(shí)現(xiàn)兩個方法:
startTimer()和ejbTimeOut。
在startTimer()方法里面,我們通過EJBCONTEXT取得一個TimerService然后創(chuàng)建一個Timer。這個timer將在 2005,9月19日晚上8點(diǎn)過5分觸發(fā),觸發(fā)后,EJB容器會調(diào)用ejbTimeOut()方法運(yùn)行具體的商業(yè)邏輯,并且這個Timer會在80000 毫秒后再次觸發(fā)。
javax.ejb.TimerService ts=this.getSessionContext().getTimerService(); |
![]() ![]() |
![]() |
基于WAS Scheduler實(shí)現(xiàn)定時服務(wù),需要配置一個scheduler,在WAS管理控制臺展開資源,點(diǎn)scheduler,新建一個scheduler指定 名稱、JNDI名、數(shù)據(jù)源JNDI名(這里可以用前面設(shè)置的jndi/testtimer)和表前綴,跟前面設(shè)置服務(wù)器EJB定時服務(wù)容器設(shè)置類似。
設(shè)置完成后就可以使用scheduler來實(shí)現(xiàn)定時服務(wù)了。
在EJB模塊中創(chuàng)建一個無狀態(tài)會話bean,該 bean 實(shí)現(xiàn)了 com.ibm.websphere.scheduler.TaskHandler 遠(yuǎn)程接口中的 process() 方法。將您要創(chuàng)建的業(yè)務(wù)邏輯放入 process() 方法中。當(dāng)運(yùn)行任務(wù)時,將調(diào)用 process() 方法。Home 和 Remote 接口在部署描述符 bean 中必須設(shè)置如下:
com.ibm.websphere.scheduler.TaskHandlerHome |
通過使用以下示例工廠方法創(chuàng)建 BeanTaskInfo 接口的一個實(shí)例。 使用 JSP 文件、servlet 或 EJB 組件創(chuàng)建實(shí)例,如以下代碼示例所示。此代碼必須與先前創(chuàng)建的 TaskHandler EJB 模塊位于同一應(yīng)用程序中:
Object schedulerObj = initialContext.lookup("java:comp/env/Scheduler"); |
注: 創(chuàng)建 BeanTaskInfo 對象并不會將任務(wù)添加到持久存儲中。它將為必要的數(shù)據(jù)創(chuàng)建一個占位符。直到調(diào)用調(diào)度程序中的 create() 方法,才會將任務(wù)添加到持久存儲中。設(shè)置 BeanTaskInfo 對象中的參數(shù)。 這些參數(shù)定義了調(diào)用哪些會話 bean 以及何時調(diào)用它們。TaskInfo 接口包含可用于控制任務(wù)執(zhí)行的各種 set() 方法,其中包括運(yùn)行任務(wù)的時間以及運(yùn)行任務(wù)時它執(zhí)行的操作。
BeanTaskInfo 接口要求使用 setTaskHandler 方法設(shè)置 TaskHandler JNDI 名稱或 TaskHandlerHome。如果使用 WASScheduler MBean API 來設(shè)置任務(wù)處理程序,則 JNDI 名稱必須是標(biāo)準(zhǔn)的全局 JNDI 名稱。
使用 TaskInfo 接口 API 方法設(shè)置參數(shù),如以下代碼示例所示:
java.util.Date startDate = new java.util.Date(System.currentTimeMillis()+30000); |
那么EJB容器將在當(dāng)前時間的30000毫秒后觸發(fā)process方法,在taskinfo里面可以設(shè)置一些其他schduler的屬性,比如運(yùn)行次數(shù),運(yùn)行間隔等。
文章里涉及的J2EE標(biāo)準(zhǔn)的定時服務(wù)程序在附件的testTimer.ear里面,使用WAS Scheduler的程序在附件的AccountReport.ear里。其中AccountReport.ear是使用WAS自帶的Sample程序修改后的程序。