2014年5月26日 星期一

Quartz工作排程工具介紹(一)-簡單整合進Tomcat

因為工作上的需要,第一次接觸Quartz,這裡就先來簡單介紹一下我對Quartz近期的學習使用心得,以下內容若有任何錯誤,也請不吝指教,畢竟是第一次接觸,可能還有許多地方學習不足。
Quartz是一個Open Source 的工作排程(job schedule) library或稱框架,它讓我們更容易的去安排、管理多個job(可以是幾個而已,也可能成千上萬個)的執行時間,以及怎麼執行(多久執行一次)等。名詞解釋不用說太多,趕快進入正題,該怎麼開始使用它來進行工作排程,接下來的說明是以整合進Web Application為主,我使用的Web Server是Tomcat 7.0。
  1. 先至Quartz的官網 下載最新的Jar檔,目前最新的版本為Quartz 2.2.1,下載下來請將lib底下的jar檔複製到WEB-INF/lib底下,我的2.2.1版分別有以下這些jar檔: 
    1. quartz-2.2.1
    2. quartz-jobs-2.2.1
    3. slf4j-api-1.6.6
    4. slf4j-log4j12-1.6.6
    5. c3p0-0.9.1.1
  2. 在Web.xml內加入如下的listener設定:
    <listener>
       <listener-class>     
         org.quartz.ee.servlet.QuartzInitializerListener     
       </listener-class>
    </listener>
    
  3. 在src根目錄下新增一個quartz.properties檔案,用來設定排程器(scheduler)的一些參數,我的設定檔內容簡單如下(這邊盡量先以最簡單的方式完成設定):
    <!--當程式中有多個scheduler時,用來區別不同scheduler  -->
    org.quartz.scheduler.instanceName = MyScheduler
    <!--同一時間能夠並行執行的job個數,規定是只要正的整數值,但一般是限定在1~100  -->
    org.quartz.threadPool.threadCount = 5
    <!--設定儲存scheduling資訊(job,triggers,calendars)的位置,這邊就如一般設定儲存在記憶體中  -->
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
    # ----------------------------- Plugins --------------------------- #
    <!--Quartz並沒有內建的logging實作,所以務必指定幫我們執行log的class -->
    org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
    <!-- Trigger在fire(發起)job執行時,要顯示的log資訊內容及格式 -->
    org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
    <!-- Trigger在job完成執行時,要顯示的log資訊內容及格式 -->
    org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger {1}.{0} completed firing job {6}.{5} at {4, date, yyyy-MM-dd HH:mm:ss.SSS} with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}  
    <!-- Trigger在fire(發起)job執行卻misFire時,要顯示的log資訊內容及格式 -->
    org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger {1}.{0} misfired job {6}.{5} at: {4, date, yyyy/MM/dd HH:mm:ss }. Should have fired at: {3, date,yyyy/MM/dd HH:mm:ss} 
    <!--此plugin 會從預設檔名為quartz_data.xml的設定檔讀取jobs和triggers設定並加至scheduler中 -->
    org.quartz.plugin.jobInitializer.class=org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
  4. 寫一個自己的job class,此class必須實作org.quartz.Job 這個interface,我的code如下:
    package com.xxx.util.Quartz.job;
    
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    
    import org.quartz.DisallowConcurrentExecution;
    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    
    @DisallowConcurrentExecution
    public class TestJob2 implements Job{
    
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
      Calendar expectedDateTime=Calendar.getInstance();
      expectedDateTime.add(Calendar.MILLISECOND, 5000);
      Long expectedExecuteTime= expectedDateTime.getTimeInMillis();
      Date date=new Date();
      
      while (expectedExecuteTime!=date.getTime() && expectedExecuteTime > date.getTime())
      {
       
       long leftMilliSecond = expectedExecuteTime-date.getTime();
       System.out.println("Thread name:"+Thread.currentThread().getName()+ "\n There is still have "+leftMilliSecond/1000.0 +" seconds");
       
       try {
        Thread.sleep(1000);
       } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
       }
       date=new Date();
      }
      SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
      System.out.println("I am job2 ,It's "+sdf.format(new Date()));
     }
    }
  5. 最後,新增一個quartz_data.xml(預設檔名)放在src根目錄下,內容如下:
    <job-scheduling-data version="1.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData" xsi:schemalocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd">
      
       <schedule>
            <job>
                <name>TestJob</name>
                <job-class>com.xxx.util.Quartz.job.TestJob2</job-class>
            </job>
            <trigger>
                <cron>
                    <name>CronTestJob</name>
                    <job-name>TestJob</job-name>
                    <cron-expression>0/5 * * * * ?</cron-expression>
                </cron>
            </trigger>
        </schedule>
    
    </job-scheduling-data>
    
    此設定是安排job每五秒執行一次,cron-expression請參考 cron-expression1cron-expression詳細說明
完成以上五個步驟後,即可以啟動你的tomcat,看看job是否按照排程設定在正確的時間執行!

PS:下面列出兩個對大家可能有用的參考網址
1.Quartz 官網內的Configuration Reference,裏頭按照不同主題將configuration做詳細的分類說明
2.Quartz 2.2.1 API

3 則留言:

  1. 您好依照您上面的設定之後跑出了下面這一串
    嚴重: Exception sending context initialized event to listener instance of class org.quartz.ee.servlet.QuartzInitializerListener
    java.lang.NoClassDefFoundError: javax/transaction/UserTransaction
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
    at java.lang.Class.privateGetPublicMethods(Unknown Source)
    at java.lang.Class.getMethods(Unknown Source)
    at java.beans.Introspector.getPublicDeclaredMethods(Unknown Source)
    at java.beans.Introspector.getTargetMethodInfo(Unknown Source)
    at java.beans.Introspector.getBeanInfo(Unknown Source)
    at java.beans.Introspector.getBeanInfo(Unknown Source)
    at org.quartz.impl.StdSchedulerFactory.setBeanProps(StdSchedulerFactory.java:1393)
    at org.quartz.impl.StdSchedulerFactory.instantiate(StdSchedulerFactory.java:1057)
    at org.quartz.impl.StdSchedulerFactory.getScheduler(StdSchedulerFactory.java:1519)
    at org.quartz.ee.servlet.QuartzInitializerListener.contextInitialized(QuartzInitializerListener.java:171)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4887)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5381)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
    Caused by: java.lang.ClassNotFoundException: javax.transaction.UserTransaction
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1714)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)
    ... 21 more

    五月 28, 2014 3:51:57 下午 org.apache.catalina.core.StandardContext startInternal
    嚴重: Error listenerStart
    五月 28, 2014 3:51:57 下午 org.apache.catalina.core.StandardContext startInternal
    嚴重: Context [/testjquery] startup failed due to previous errors

    請問有解嗎,謝謝。

    回覆刪除
    回覆
    1. 加入jta-1.1.jar以解決錯誤。
      但出現
      log4j:WARN No appenders could be found for logger (org.quartz.ee.servlet.QuartzInitializerListener).
      log4j:WARN Please initialize the log4j system properly.
      log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
      且無資料顯示

      刪除
    2. 您好,你的提示訊息看起來只是沒有設定 log4j的properties檔,但理論上應該還是可以正常的啟動Quartz,所以你設定的job有開始跑嗎?

      刪除