熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Quartz調度框架應用總結

2013-11-23 19:16:41  來源: Java核心技術 

  前一段時間項目需要做一個定時發送消息的功能該功能依附於Web應用上即當Web應用啟動時該應用就開始作用起先決定使用javautilTimer和javautilTimerTask來實現但是研究了一下以後發現Java Timer的功能比較弱而且其線程的范圍不受Web應用的約束後來發現了Quartz這個開源的調度框架非常有趣

  首先我們要得到Quartz的最新發布版目前其最新的版本是我們可以從以下地址獲得它的完整下載包包中可謂湯料十足不僅有我們要的quartzjar更包含多個例程和詳細的文檔從API到配置文件的XSD一應俱全感興趣的朋友也可以在src目錄下找到該項目的源碼一看究竟

  廢話少說下面就來看一看這個東東是怎麼在Java Web Application中得以使用的

  首先不得不提出的是Quartz的三個核心概念調度器觸發器作業讓我們來看看他們是如何工作的吧
    一.作業總指揮——調度器
    .Scheduler接口
    該接口或許是整個Quartz中最最上層的東西了它提攜了所有觸發器和作業使它們協調工作每個Scheduler都存有JobDetail和Trigger的注冊一個Scheduler中可以注冊多個JobDetail和多個Trigger這些JobDetail和Trigger都可以通過group name和他們自身的name加以區分以保持這些JobDetail和Trigger的實例在同一個Scheduler內不會沖突所以每個Scheduler中的JobDetail的組名是唯一的本身的名字也是唯一的(就好像是一個JobDetail的ID)Trigger也是如此

  Scheduler實例由SchedulerFactory產生一旦Scheduler實例生成後我們就可以通過生成它的工廠來找到該實例獲取它相關的屬性下面的代碼為我們展示了如何從一個Servlet中找到SchedulerFactory並獲得相應的Scheduler實例通過該實例我們可以獲取當前作業中的testmode屬性來判斷該作業是否工作於測試模式
    view plaincopy to clipboardprint?
    //從當前Servlet上下文中查找StdSchedulerFactory

  ServletContext ctx=requestgetSession()getServletContext();

  StdSchedulerFactory factory = (StdSchedulerFactory) ctxgetAttribute(orgquartzimplStdSchedulerFactoryKEY);

  Scheduler sch = null;

  try {

  //獲取調度器

  sch = factorygetScheduler(SchedulerName);

  //通過調度器實例獲得JobDetail注意領會JobDetailName和GroupName的用法

  JobDetail jd=schgetJobDetail(JobDetailName GroupName);

  Map jobmap=jdgetJobDataMap();

  istest=jobmapget(testmode)+;

  } catch (Exception se) {

  //如果得不到當前作業則從配置文件中讀取testmode

  ReadXML(jobxml)get(jobtestmode);

  }

  //從當前Servlet上下文中查找StdSchedulerFactory

  ServletContext ctx=requestgetSession()getServletContext();

  StdSchedulerFactory factory = (StdSchedulerFactory) ctxgetAttribute(orgquartzimplStdSchedulerFactoryKEY);

  Scheduler sch = null;

  try {

  //獲取調度器

  sch = factorygetScheduler(SchedulerName);

  //通過調度器實例獲得JobDetail注意領會JobDetailName和GroupName的用法

  JobDetail jd=schgetJobDetail(JobDetailName GroupName);

  Map jobmap=jdgetJobDataMap();

  istest=jobmapget(testmode)+;

  } catch (Exception se) {

  //如果得不到當前作業則從配置文件中讀取testmode

  ReadXML(jobxml)get(jobtestmode);

  }
    Scheduler實例生成後它處於standby模式需要調用其start方法來使之投入運作 view plaincopy to clipboardprint?
    public class SendMailShedule{

  //設置標准SchedulerFactory

  static SchedulerFactory schedFact = new orgquartzimplStdSchedulerFactory();

  static Scheduler sched;

  public static void run()throws Exception{

  //生成Scheduler實例

  sched = schedFactgetScheduler();

  //創建一個JobDetail實例對應的Job實現類是SendMailJob

  JobDetail jobDetail = new JobDetail(myJobschedDEFAULT_GROUPSendMailJobclass);

  //設置CronTrigger利用Cron表達式設定觸發時間

  CronTrigger trigger = new CronTrigger(myTriggertest * ?);

  schedscheduleJob(jobDetail trigger);

  schedstart();

  }

  public static void  stop()throws Exception{

  schedshutdown();

  }

  }

  public class SendMailShedule{

  //設置標准SchedulerFactory

  static SchedulerFactory schedFact = new orgquartzimplStdSchedulerFactory();

  static Scheduler sched;

  public static void run()throws Exception{

  //生成Scheduler實例

  sched = schedFactgetScheduler();

  //創建一個JobDetail實例對應的Job實現類是SendMailJob

  JobDetail jobDetail = new JobDetail(myJobschedDEFAULT_GROUPSendMailJobclass);

  //設置CronTrigger利用Cron表達式設定觸發時間

  CronTrigger trigger = new CronTrigger(myTriggertest * ?);

  schedscheduleJob(jobDetail trigger);

  schedstart();

  }

  public static void  stop()throws Exception{

  schedshutdown();

  }

  }另外我們也可以通過監聽器來跟蹤作業和觸發器的工作狀態

  二.作業及其相關

  . Job

  作業實際上是一個接口任何一個作業都可以寫成一個實現該接口的類並實現其中的execute()方法來完成具體的作業任務

  . JobDetail

  JobDetail可以指定我們作業的詳細信息比如可以通過反射機制動態的加載某個作業的實例可以指定某個作業在單個調度器內的作業組名稱和具體的作業名稱可以指定具體的觸發器

  一個作業實例可以對應多個觸發器(也就是說學校每天點放一次眼保健操錄音下午點半可以再放一次)但是一個觸發器只能對應一個作業實例(點鐘的時候學校不可能同時播放眼保健操和廣播體操的錄音)

  . JobDataMap

  這是一個給作業提供數據支持的數據結構使用方法和javautilMap一樣非常方便當一個作業被分配給調度器時JobDataMap實例就隨之生成

  Job有一個StatefulJob子接口代表有狀態的任務該接口是一個沒有方法的標簽接口其目的是讓Quartz知道任務的類型以便采用不同的執行方案無狀態任務在執行時擁有自己的JobDataMap拷貝對JobDataMap的更改不會影響下次的執行而有狀態任務共享共享同一個JobDataMap實例每次任務執行對JobDataMap所做的更改會保存下來後面的執行可以看到這個更改也即每次執行任務後都會對後面的執行發生影響

  正因為這個原因無狀態的Job可以並發執行而有狀態的StatefulJob不能並發執行這意味著如果前次的StatefulJob還沒有執行完畢下一次的任務將阻塞等待直到前次任務執行完畢有狀態任務比無狀態任務需要考慮更多的因素程序往往擁有更高的復雜度因此除非必要應該盡量使用無狀態的Job

  如果Quartz使用了數據庫持久化任務調度信息無狀態的JobDataMap僅會在Scheduler注冊任務時保持一次而有狀態任務對應的JobDataMap在每次執行任務後都會進行保存

  JobDataMap實例也可以與一個觸發器相關聯這種情況下對於同一作業的不同觸發器我們可以在JobDataMap中添加不同的數據以便作業在不同時間執行時能夠提供更為靈活的數據支持(學校上午放眼保健操錄音第一版下午放第二版)

  不管是有狀態還是無狀態的任務在任務執行期間對Trigger的JobDataMap所做的更改都不會進行持久也即不會對下次的執行產生影響

  三.觸發器

  Trigger是一個抽象類它有三個子類SimpleTriggerCronTrigger和NthIncludedDayTrigger前兩個比較常用

  SimpleTrigger這是一個非常簡單的類我們可以定義作業的觸發時間並選擇性的設定重復間隔和重復次數

  CronTrigger這個觸發器的功能比較強大而且非常靈活但是你需要掌握有關Cron表達式的知識如果你是一個Unix系統愛好者你很可能已經具備這種知識但是如果你不了解Cron表達式請看下面的Cron詳解

  Cron表達式由個由空格分隔的時間字段組成如表所示

  表 Cron表達式時間字段


  位置

  時間域名

  允許值

  允許的特殊字符

  

  秒

  

   * /

  

  分鐘

  

   * /

  

  小時

  

   * /

  

  日期

  

   * ? / L W C

  

  月份

  

   * /

  

  星期

  

   * ? / L C #

  

  年(可選)

  空值

   * /

  Cron表達式的時間字段除允許設置數值外還可使用一些特殊的字符提供列表范圍通配符等功能細說如下

  ●星號(*)可用在所有字段中表示對應時間域的每一個時刻例如*在分鐘字段時表示每分鐘

  ●問號(?)該字符只在日期和星期字段中使用它通常指定為無意義的值相當於點位符

  ●減號()表達一個范圍如在小時字段中使用則表示從

  ●逗號()表達一個列表值如在星期字段中使用MONWEDFRI則表示星期一星期三和星期五

  ●斜槓(/)x/y表達一個等步長序列x為起始值y為增量步長值如在分鐘字段中使用/則表示為/在分鐘字段中表示你也可以使用*/y它等同於/y

  ●L該字符只在日期和星期字段中使用代表Last的意思但它在兩個字段中意思不同L在日期字段中表示這個月份的最後一天如一月的非閏年二月的如果L用在星期中則表示星期六等同於但是如果L出現在星期字段裡而且在前面有一個數值X則表示這個月的最後X天例如L表示該月的最後星期五

  ●W該字符只能出現在日期字段裡是對前導日期的修飾表示離該日期最近的工作日例如W表示離該月號最近的工作日如果該月號是星期六則匹配號星期五如果日是星期日則匹配號星期一如果號是星期二那結果就是號星期二但必須注意關聯的匹配日期不能夠跨月如你指定W如果號是星期六結果匹配的是號星期一而非上個月最後的那天W字符串只能指定單一日期而不能指定日期范圍

  ●LW組合在日期字段可以組合使用LW它的意思是當月的最後一個工作日

  ●井號(#)該字符只能在星期字段中使用表示當月某個工作日#表示當月的第三個星期五(表示星期五#表示當前的第三個)#表示當月的第五個星期三假設當月沒有第五個星期三忽略不觸發

  ● C該字符只在日期和星期字段中使用代表Calendar的意思它的意思是計劃所關聯的日期如果日期沒有被關聯則相當於日歷中所有日期例如C在日期字段中就相當於日歷日以後的第一天C在星期字段中相當於星期日後的第一天Cron表達式對特殊字符的大小寫不敏感對代表星期的縮寫英文大小寫也不敏感下面給出一些完整的Cron表示式的實例

  表 Cron表示式示例


  表示式

  說明

   * * ?

  每天點運行

   ? * *

  每天:運行

   * * ?

  每天:運行

   * * ? *

  每天:運行

   * * ?

  在年的每天運行

   * * * ?

  每天點到點之間每分鐘運行一次開始於:結束於:

   / * * ?

  每天點到點每分鐘運行一次開始於:結束於:

   / * * ?

  每天點到點每分鐘運行一次此外每天點到點每鐘也運行一次

   * * ?

  每天:點到:每分鐘運行一次

   ? WED

  月每周三的:分到:每分鐘運行一次

   ? * MONFRI

  每周一五的:分運行

   * ?

  每月:分運行

   L * ?

  每月最後一天:分運行

   ? * L

  每月最後一個星期五:分運行

   ? * L

  在年每個月的最後一個星期五的:分運行

   ? * #

  每月第三個星期五的:分運行



    好說了這麼多最後讓我們來看看如何在Web應用中使用Quartz
 
 


    由於Scheduler的配置相當的個性化所以在Web應用中我們可以通過一個quartzproperties文件來配置QuartzServlet不過之前讓我們先來看看webxml中如何配置
    webxml view plaincopy to clipboardprint?
    <servlet>
            <servletname>
                 QuartzInitializer
            </servletname>
            <displayname>
                 Quartz Initializer Servlet
            </displayname>
            <servletclass>
                 orgquartzeeservletQuartzInitializerServlet
            </servletclass>
            <loadonstartup>
                
            </loadonstartup>
            <initparam>
                <paramname>configfile</paramname>
                <paramvalue>/quartzproperties</paramvalue>
            </initparam>
            <initparam>
                <paramname>shutdownonunload</paramname>
                <paramvalue>true</paramvalue>
            </initparam>
            <initparam>
                <paramname>startscheduleronload</paramname>
                <paramvalue>true</paramvalue>
            </initparam>
        </servlet>
    <servlet>
            <servletname>
                 QuartzInitializer
            </servletname>
            <displayname>
                 Quartz Initializer Servlet
            </displayname>
            <servletclass>
                 orgquartzeeservletQuartzInitializerServlet
            </servletclass>
            <loadonstartup>
                
            </loadonstartup>
            <initparam>
                <paramname>configfile</paramname>
                <paramvalue>/quartzproperties</paramvalue>
            </initparam>
            <initparam>
                <paramname>shutdownonunload</paramname>
                <paramvalue>true</paramvalue>
            </initparam>
            <initparam>
                <paramname>startscheduleronload</paramname>
                <paramvalue>true</paramvalue>
            </initparam>
        </servlet>
    這裡loadonstartup是指定QuartzServlet是否隨應用啟動表示否正數表示隨應用啟動數值越小則優先權越高初始化參數中configfile裡面可以指定QuartzServlet的配置文件這裡我們用的是quartzpropertiesshutdownonunload表示是否在卸載應用時同時停止調度該參數推薦true否則你的tomcat進程可能停不下來startscheduleronload表示應用加載時就啟動調度器如果為false則quartzproperties中指定的調度器在用戶訪問這個Servlet之後才會加載在此之前如果你通過ServletContext查找SchedulerFactory是可以找到的但是要得到具體的Scheduler那麼你一定會發現Jvm拋出了一個NullPointerExcetion
    下面就來看看quartzproperties的真面目
    quartzproperties
    view plaincopy to clipboardprint?orgquartzschedulerinstanceName = PushDBScheduler  orgquartzschedulerinstanceId = one    orgorgquartzthreadPoolclass = orgquartzsimplSimpleThreadPool   orgquartzthreadPoolthreadCount =   orgquartzthreadPoolthreadPriority =     orgorgquartzpluginjobInitializerclass = orgquartzpluginsxmlJobInitializationPlugin   orgquartzpluginjobInitializerfileName = quartz_jobxml  orgquartzschedulerinstanceName = PushDBScheduler
    orgquartzschedulerinstanceId = one
    orgquartzthreadPoolclass = orgquartzsimplSimpleThreadPool
    orgquartzthreadPoolthreadCount =
    orgquartzthreadPoolthreadPriority =
    orgquartzpluginjobInitializerclass = orgquartzpluginsxmlJobInitializationPlugin
    orgquartzpluginjobInitializerfileName = quartz_jobxml
    我想不用多說大家都看出來了首先配置了基本的Scheduler實例名並分配了ID然後為這個調度器設定了線程池後面是初始化插件初始化插件是Quartz非常實用的功能你可以用這個功能來實現Quartz的擴展性這裡配置的插件是讀取job XML文件讓調度器自動載入Job這個插件現在支持讀取多個job XML文件但是我現在還沒有試過感興趣的讀者可以自己嘗試另外就是有一個scanInterval屬性表示每隔幾秒自動掃描一次job XML文件我現在也沒有試過感興趣的讀者可以自己試驗一下注意該參數設定為表示不掃描
    最後我們來看看job XML文件這裡以quartz_jobxml為例
    quartz_jobxml
    view plaincopy to clipboardprint?
    <quartz>
        <job>
            <jobdetail>
                <name>ScanItemsInDB</name>
                <group>Scanning</group>
                <jobclass>comtestquartzScanDB</jobclass>
                <jobdatamap allowstransientdata=true>
                     <entry>
                      <key>testmode</key>
                      <value>true</value>
                     </entry>
                </jobdatamap>
            </jobdetail>
            <trigger>
                <cron>
                    <name>t</name>
                    <group> Scanning </group>
                    <jobname> ScanItemsInDB </jobname>
                    <jobgroup> Scanning </jobgroup>
                    <cronexpression> / * * * ?</cronexpression>
                </cron>
            </trigger>
        </job>
    </quartz>
    <quartz>
        <job>
            <jobdetail>
                <name>ScanItemsInDB</name>
                <group>Scanning</group>
                <jobclass>comtestquartzScanDB</jobclass>
                <jobdatamap allowstransientdata=true>
                     <entry>
                      <key>testmode</key>
                      <value>true</value>
                     </entry>
                </jobdatamap>
            </jobdetail>
            <trigger>
                <cron>
                    <name>t</name>
                    <group> Scanning </group>
                    <jobname> ScanItemsInDB </jobname>
                    <jobgroup> Scanning </jobgroup>
                    <cronexpression> / * * * ?</cronexpression>
                </cron>
            </trigger>
        </job>
    </quartz>
    這個文件真是非常顯而易見了我就不多說了大家自己研究吧
    然後你只要自己寫一下ScanDB這個類就可以了
    ScanDBjava view plaincopy to clipboardprint?
    public class ScanDB implements Job {
 
         public void execute(JobExecutionContext context) throws JobExecutionException {
                //你的代碼
        }
    }
    public class ScanDB implements Job {
 
         public void execute(JobExecutionContext context) throws JobExecutionException {
       //你的代碼
     }
    }
    注意JobExecutionContext這個類這個類是用來存取任務執行時的相關信息的從中我們可以獲取當前作業的TriggerSchedulerJobDataMap等等
    當然Scheduler也有對應的SchedulerContext具體的用途很像ServletContext有興趣的讀者自己研究吧
 
    另外就是可以提供一個提示在一個作業執行的時候你就可以設定另外一個調度器去執行另一個Job這樣你可以每個一段時間掃描一下數據庫然後看一看數據庫裡有沒有下一個時間段待發的郵件然後調用一個新的調度器實例以便在指定的發送時間將其發送出去
 


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26569.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.