Quartz學習筆記

什麼是job schedule system?html


    job schedule system是負責在提早定義的時間運行或者通知某個應用組件的系統。舉個樣例來講。比方在每週一早上930發送email通知客戶最新的業務狀況。java

java.util.Timerjava.util.TimerTask
    TimerTimerTask是可以完畢job schedule的兩個jdk提供的類,只是這不能稱爲一個systemTimerTimerTask是很是easy的,不直接支持持久化任務,線程池和相似日曆(calendar-like)的計劃安排,在完畢一些高級功能上開發者要進行大量的擴展。web

Quartz的簡介
    Quartzopensymphony組織專攻job scheduling領域又一個開源利器,可以到http://www.opensymphony.com/quartz查看具體信息。Quartz是輕量級的組件,開發者僅僅需要載入單獨的jar包就可以利用Quartz強大的日程安排功能。固然,假如你爲Quartz配備了數據庫持久化任務的特性,Quartz也可以很是好的利用這一點。從而在機器從新啓動後還可以記住你原先安排的計劃。sql

    Quartz中咱們接觸最多的接口使Scheduler接口,該接口的提供了計劃安排的功能。比方schedule/unschedule計劃、start/pause/stop Scheduler.數據庫

    Quartz提供一些常用的Listener(JobListener,TriggerListener,SchedulerListener)用於全然的監視計劃安排和運行狀況。post

開始咱們的Quartz之旅spa

·  HelloWorld example:線程

想必你們很是想看一個HelloWorld的樣例了吧。那麼仍是以HellowWorld開始。unix

import java.util.Date;
 import org.quartz.Job;
 import org.quartz.JobDetail;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.quartz.Scheduler;
 import org.quartz.SchedulerFactory;
 import org.quartz.Trigger;
 import org.quartz.impl.StdSchedulerFactory;
 /**
  * @author snowway
  * @version $Id$
  */
 public class SayHelloWorldJob implements Job{
     /*
      * (non-Javadoc)
      *
      * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
      */     public void execute(JobExecutionContext context) throws JobExecutionException{
         System.out.println("hello world!");
     }postgresql

     public static void main(String[] args) throws Exception{
         SchedulerFactory factory = new StdSchedulerFactory();
         Scheduler scheduler = factory.getScheduler();

         JobDetail jobDetail = new JobDetail("SayHelloWorldJob",
                 Scheduler.DEFAULT_GROUP,
                 SayHelloWorldJob.class);

         Trigger trigger = new SimpleTrigger("SayHelloWorldJobTrigger",
                 Scheduler.DEFAULT_GROUP,
                 new Date(),
                 null,
                 0,
                 0L);
         scheduler.scheduleJob(jobDetail, trigger);
         scheduler.start();
     }
 }

爲了簡單起見,我把main方法寫在SayHelloWorldJob中了,運行SayHelloWorldJob可以看到控制檯打印hello world.

·  回想Hello World example:

 Job是什麼?
 接口Job是每個業務上需要運行的任務需要實現的接口,該接口僅僅有一個方法:

 package org.quartz;

    public interface Job {

      public void execute(JobExecutionContext context)
        throws JobExecutionException;
    }

 execute方法也就是當時間到達後。Quartz回調的方法,咱們使SayHelloWorldJob實現Job接口以提供打印功能

 JobDetail是什麼? JobDetail描寫敘述了一個任務具體的信息,比方名稱,組名等等。


 JobDetail jobDetail = new JobDetail("SayHelloWorldJob",
                 Scheduler.DEFAULT_GROUP,
                 SayHelloWorldJob.class);
 在上面的構造方法中。第一個是任務的名稱,第二個是組名,第三個就是實際當任務需要運行的回調類。

 Trigger是什麼?
 Trigger顧名思義就是觸發器。Quartz有個很是好的想法就是分離了任務和任務運行的條件。

Trigger就是控制任務運行條件的類。當Trigger以爲運行條件知足的時刻。Trigger會通知相關的Job去運行。分離的優勢是:
 1.你可以爲某個Job關聯多個Trigger,當中不論什麼一個條件知足都可以觸發job運行,這樣可以完畢一些組合的高級觸發條件
 2.Trigger失效後(比方:一個永遠都不能知足的條件),你沒必要去聲明一個新的job。取代的是你可以爲job關聯一個新的Triggerjob可以繼續運行。

 眼下的Quartz實現中。存在兩種Trigger,SimpleTriggerCronTrigger,SimpleTrigger用來完畢一些比方固定時間運行的任務。比方:從現在開始1分鐘後等等;而CronTrigger(沒錯,和unixcron進程的含意同樣)用來運行calendar-like的任務。比方:每週五下午300,每個月最後一天等等。

 Trigger trigger = new SimpleTrigger("SayHelloWorldJobTrigger",
                 Scheduler.DEFAULT_GROUP,
                 new Date(),
                 null,
                 0,
                 0L); 這個構造方法中,第一個是Trigger的名稱。第二個是Trigger的組名,第三個是任務開始時間。第四個是結束時間,第五個是反覆
 次數(使用SimpleTrigger.REPEAT_INDEFINITELY常量表示無限次),最後一個是反覆週期(單位是毫秒),那麼這樣就建立
 了一個立馬並僅僅運行一次的任務。

 scheduler.scheduleJob(jobDetail, trigger);
 這條語句就是把jobTrigger關聯,這樣當Trigger以爲應該觸發的時候就會調用(其實是Scheduler調用)job.execute方法了。

 scheduler.start();
 千萬別忘了加上上面的語句,這條語句通知Quartz使安排的計劃生效。

 關於execute方法的參數JobExecutionContext
 JobExecutionContext就和很是多Context結尾的類功能同樣。提供的運行時刻的上下文環境。JobExecutionContext中有
 Scheduler,JobDetail,Trigger等很是多對象的引用,從而當你在execute方法內部須需要這些對象的時刻提供的便利。

 JobDetailTriggernamegroup Scheduler實例相應了很是多jobtrigger的實例,爲了方便的區分,Quartz使用namegroup這兩個特性,正如你想向的同樣。
 同一個group下不能有兩個一樣nameJobDetailTrigger同理
 同一個Scheduler下不能有兩個一樣groupJobDetail,Trigger同理
 JobDetailTrigger的全然限定名爲:group + name

·  更深刻的思考...


HelloWorld的樣例還不足以說明一些問題,一些人可能會這樣問:假如execute方法中需要一些額外的數據怎麼辦?比方說execute
 中但願發送一封郵件。但是我需要知道郵件的發送者、接收者等信息?

 存在兩種解決方式:
 1.JobDataMap類:
   每個JobDetail都關聯了一個JobDataMap實例,JobDataMapjava.util.Map的子類,基本上是提供key-value形式的數據。並提供了一些便利方法(主要是對java基本數據類型的支持,如put(String key,int value))。當開發者建立JobDetail的時候。可以把附加信息放到JobDataMap中。那麼在execute方法中可以依據key找到需要的值。


   JobDetail job = new JobDetail....
   job.getJobDataMap().put("from","snowway@vip.sina.com");
   ...  

execute
   String from = jobExecutionContext.getJobDetail().getJobDataMap().getString("from");
   ....

   只是,當你使用數據庫存儲JobDetail的時候(默認狀況下使用RAM),這裏有一個致命的弱點。你不能把沒有實現java.io.Serializable的對象放入JobDataMap中。因爲Quartz將使用Blob字段保存(也可以經過配置文件關閉)序列化過的JobDataMap中的對象。比方你在execute方法中需要一個java.sql.Connection接口實例。這樣的狀況也是廣泛的,那麼一般狀況下你不能把Connection放入JobDataMap。即便你僅僅想在execute中使用。

(注:讀者可臨時以爲上面這段話是正確的。然而可以經過指示quartz改變這樣的行爲。那屬於高級話題)

 2.假如你需要一個java.sql.Connection,用於在execute中完畢某些操做。那麼你可以把Connection放入QuartzSchedulerContext中,execute也可以訪問,並且Quartz不會持久化SchedulerContext中的不論什麼東西。

   scheduler.getContext().put("java.sql.Connection",connection);  

 execute
   Connection con = (Connection)jobExecutionContext.getScheduler().getContext().get("java.sql.Connection");

Java 中已經有一個 timer 類可以用來進行運行計劃。定時任務。咱們所要作的僅僅是 繼承 java.util.TimerTask 類。例如如下所看到的:

package com.yourcompany.scheduling; 


 

import java.util.Calendar; 

import java.util.Date;

import java.util.Timer;

import java.util.TimerTask;


 

public class ReportGenerator extends TimerTask { 


 

 public void run() { 

    System.out.println("Generating report");

    //TODO generate report

 }


 

} 


 

class MainApplication { 


 

 public static void main(String[] args) {

    Timer timer new Timer();

    Calendar date = Calendar.getInstance();

    date.set(

      Calendar.DAY_OF_WEEK,

      Calendar.SUNDAY

    );

    date.set(Calendar.HOUR, 0);

    date.set(Calendar.MINUTE, 0);

    date.set(Calendar.SECOND, 0);

    date.set(Calendar.MILLISECOND, 0);

    // Schedule to run every Sunday in midnight

    timer.schedule(

      new ReportGenerator(),   // TimerTask

      date.getTime(),          // Timer

      1000 * 60 * 60 * 24 * 7   // delay

    );

 }

}

 

 

 這裏有幾個問題。咱們的類繼承了TimerTask ,而timerTask 也是實現了 java.lang.Runnable 接口。

咱們所要作的僅僅是在咱們本身的類裏重置 run() 方法。因此咱們的TimerTask類事實上是一種線程,但線程的調度每每不是依照咱們但願來實現的,因爲一些垃圾收集等緣由,咱們計劃的時間點。卻沒有運行必要的任務。

這樣會產生一些問題。儘管,Timer 類也提供了scheduleAtFixedRate() 方法用來在垃圾收集後可以高速的追上任務進度,但這個不必定是咱們所需要的。特別是在 一些 J2EE server上 Timer 是沒法控制的,因爲它不在容器的權責範圍內。

另外的。這個任務調度也缺少一些企業級所需要的 特殊 日期定製的功能,以及改動,查找任務的功能。

       這裏咱們要介紹的是一個開源項目:Quartz 。

       Quartz 定義了兩種 基本接口 Job 和 Trigger 。 看名字也就知道,咱們的任務必須實現 Job, 咱們的時間觸發器定義在 Trigger 內。

 看一個樣例或許能更快的瞭解他的用法: package com.yourcompany.scheduling;

 


 

import org.quartz.*; 


 

public class QuartzReport implements Job { 


 

 public void execute(JobExecutionContext cntxt) //必須實現的方法 

    throws JobExecutionException {

      System.out.println(

        "Generating report - " +

cntxt.getJobDetail().getJobDataMap().get("type")

      );

      //TODO Generate report

 }


 

 public static void main(String[] args) { 

    try {

      SchedulerFactory schedFact 

       new org.quartz.impl.StdSchedulerFactory();

      Scheduler sched = schedFact.getScheduler();

      sched.start();

      JobDetail jobDetail =

        new JobDetail(

          "Income Report",       // 任務名

          "Report Generation", // 任務組

          QuartzReport.class    //任務運行的類

        );

      jobDetail.getJobDataMap().put(

                                "type",   

                                "FULL"

                               );


 

      CronTrigger trigger new CronTrigger( 

        "Income Report",              //觸發器名

        "Report Generation"          //觸發器組

      );

      trigger.setCronExpression(     // 觸發器時間設定

        "0 0 12 ? * SUN"

      );


 

      sched.scheduleJob(jobDetail, trigger); // 運行任務 


 

    } catch (Exception e) {

      e.printStackTrace();

    }

 }

}

這裏面咱們可以看到。當咱們定義了任務運行 QuartzReport 類後。需要定一個Scheduler類用來運行計劃任務。

一個JobDetail 類來描寫敘述這個任務的信息,包含任務信息,任務所在組。任務運行的類。

而後還要定義一個 觸發器。相似的也包含觸發器名,觸發器所在組,觸發器觸發時間設定。

最後是調度器Scheduler類運行計劃任務。

基本上一個計劃任務運行的流程就完畢了。

固然。咱們還看到了上面紅色表明的內容。這些內容主要是提供在job方法運行的時候所需要的參數的提供。這裏使用了JobDataMap 類,它事實上就是實現了map的特殊應用的一個類。用法與Map 很是相似。咱們可以用 put() 輸入參數。在Job類中使用cntxt.getJobDetail().getJobDataMap().get("type") 方法獲取輸入的參數的值。這裏的cntxt 是JobExecutionContext 

就是包含任務運行上下文的一個信息類。這樣咱們的一個主要的任務運行就可以搞定了。

 

觸發器有兩類:SimpleTrigger andCronTriggerSimpleTrigger主要提供了跟 java.util.Timer 類相似的功能.

你可以在裏面定義 任務的起始時間,終止時間,任務的運行次數,任務運行的中間間隔 。

 而 CronTrigger類主要提供了更高級的任務調度時間設置,好比 每個星期天的早上7點  。CronTrigger的時間設置說明在最後來介紹。

 

如下咱們介紹一下在 J2EE 環境下怎樣來使用 Quartz 

首先。咱們要配置 web.xml ,加入 一下內容。主要是Quartz 的初始化,

<servlet>

          <servlet-name>QuartzInitializer</servlet-name>

          <display-name>Quartz Initializer Servlet</display-name>

          <servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>

          <load-on-startup>1</load-on-startup>

     </servlet>

 

而後還要有一個Quartz 的配置文件 quartz.properties 放置在 WEB-INF/classes文件夾如下。

StdScheduleFactory()會讀取它。配置例如如下 #

 

# Configure Main Scheduler Properties 

#


 

org.quartz.scheduler.instanceName = TestScheduler 

org.quartz.scheduler.instanceId = one


 

# 

# Configure ThreadPool 

#


 

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool 

org.quartz.threadPool.threadCount = 5

org.quartz.threadPool.threadPriority = 4


 

# 

# Configure JobStore 

#


 

org.quartz.jobStore.misfireThreshold = 5000

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

 

這裏咱們使用的 RAMJobStore 存儲方式。這樣假設咱們的webserver從新啓動的話,咱們所有未運行的任務信息都回丟失。固然,咱們也有另外的選擇,咱們可以把這樣的信息存儲在數據庫內,就是使用JDBCJobStoreTX

#

# Configure ThreadPool 

#

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

 

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate

org.quartz.jobStore.dataSource = myDS

org.quartz.jobStore.tablePrefix = QRTZ_


 

# 

# Configure Datasources 

#


 

org.quartz.dataSource.myDS.driver = org.postgresql.Driver

org.quartz.dataSource.myDS.URL = jdbc:postgresql:dev

org.quartz.dataSource.myDS.user = dejanb

org.quartz.dataSource.myDS.password =

org.quartz.dataSource.myDS.maxConnections 5

 

附:cronExpression配置說明

字段

 

贊成值

 

贊成的特殊字符

 

0-59

 

, - * /

 

0-59

 

, - * /

小時

 

0-23

 

, - * /

日期

 

1-31

 

, - * ? / L W C

月份

 

1-12 或者 JAN-DEC

 

, - * /

星期

 

1-7 或者 SUN-SAT

 

, - * ? / L C #

年(可選)

 

留空, 1970-2099

 

, - * /

 

Cron 的小小說明

表示方式

意義

"0 0 12 * * ?"

Fire at 12pm (noon) every day

"0 15 10 ?

 * *"

Fire at 10:15am every day

"0 15 10 * * ?

"

Fire at 10:15am every day

"0 15 10 * * ?

 *"

Fire at 10:15am every day

"0 15 10 * * ? 2005"

Fire at 10:15am every day during the year 2005

"0 * 14 * * ?"

Fire every minute starting at 2pm and ending at 2:59pm, every day

"0 0/5 14 * * ?"

Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day

"0 0/5 14,18 * * ?"

Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day

"0 0-5 14 * * ?"

Fire every minute starting at 2pm and ending at 2:05pm, every day

"0 10,44 14 ? 3 WED"

Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.

"0 15 10 ? * MON-FRI"

Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday

"0 15 10 15 * ?

"

Fire at 10:15am on the 15th day of every month

"0 15 10 L * ?

"

Fire at 10:15am on the last day of every month

"0 15 10 ? * 6L"

Fire at 10:15am on the last Friday of every month

"0 15 10 ?

 * 6L"

Fire at 10:15am on the last Friday of every month

"0 15 10 ?

 * 6L 2002-2005"

Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005

"0 15 10 ?

 * 6#3"

Fire at 10:15am on the third Friday of every month

相關文章
相關標籤/搜索