Quartz 入門詳解

入門簡介:

基本上任何公司都會用到調度這個功能, 比如我們公司需要定期執行調度生成報表, 或者比如博客什么的定時更新之類的,都可以靠Quartz來完成。正如官網所說,小到獨立應用大到大型電子商務網站, Quartz都能勝任。

Quartz體系結構:

明白Quartz怎么用,首先要了解Scheduler(調度器)、Job(任務)和Trigger(觸發器)這3個核心的概念。

  1. Job: 是一個接口,只定義一個方法execute(JobExecutionContext context),在實現接口的execute方法中編寫所需要定時執行的Job(任務), JobExecutionContext類提供了調度應用的一些信息。Job運行時的信息保存在JobDataMap實例中;

  2. JobDetail: **Quartz每次調度Job時, 都重新創建一個Job實例, 所以它不直接接受一個Job的實例,相反它接收一個Job實現類(JobDetail:描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息),以便運行時通過newInstance()的反射機制實例化Job。

  3. Trigger: **是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當且僅當需調度一次或者以固定時間間隔周期執行調度,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種復雜時間規則的調度方案:如工作日周一到周五的15:00~16:00執行調度等;
    具體cron語法參考這篇文章http://www.lxweimin.com/p/9027d067ac5b

  1. Calendar:**org.quartz.Calendar和java.util.Calendar不同, 它是一些日歷特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日歷時間點,無特殊說明后面的Calendar即指org.quartz.Calendar)。 一個Trigger可以和多個Calendar關聯, 以便排除或包含某些時間點。假設,我們安排每周星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除。針對不同時間段類型,Quartz在org.quartz.impl.calendar包下提供了若干個Calendar的實現類,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分別針對每年、每月和每周進行定義;

  2. Scheduler: **代表一個Quartz的獨立運行容器, Trigger和JobDetail可以注冊到Scheduler中, 兩者在Scheduler中擁有各自的組及名稱, 組及名稱是Scheduler查找定位容器中某一對象的依據, Trigger的組及名稱必須唯一, JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因為它們是不同類型的)。Scheduler定義了多個接口方法, 允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。Scheduler可以將Trigger綁定到某一JobDetail中, 這樣當Trigger觸發時, 對應的Job就被執行。一個Job可以對應多個Trigger, 但一個Trigger只能對應一個Job??梢酝ㄟ^SchedulerFactory創建一個Scheduler實例。Scheduler擁有一個SchedulerContext,它類似于ServletContext,保存著Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內的信息。SchedulerContext內部通過一個Map,以鍵值對的方式維護這些上下文數據,SchedulerContext為保存和獲取數據提供了多個put()和getXxx()的方法。可以通過Scheduler# getContext()獲取對應的SchedulerContext實例;

  3. ThreadPool: **Scheduler使用一個線程池作為任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。Job有一個StatefulJob子接口,代表有狀態的任務,該接口是一個沒有方法的標簽接口,其目的是讓Quartz知道任務的類型,以便采用不同的執行方案。無狀態任務在執行時擁有自己的JobDataMap拷貝,對JobDataMap的更改不會影響下次的執行。而有狀態任務共享共享同一個JobDataMap實例,每次任務執行對JobDataMap所做的更改會保存下來,后面的執行可以看到這個更改,也即每次執行任務后都會對后面的執行發生影響。正因為這個原因,無狀態的Job可以并發執行,而有狀態的StatefulJob不能并發執行,這意味著如果前次的StatefulJob還沒有執行完畢,下一次的任務將阻塞等待,直到前次任務執行完畢。有狀態任務比無狀態任務需要考慮更多的因素,程序往往擁有更高的復雜度,因此除非必要,應該盡量使用無狀態的Job。如果Quartz使用了數據庫持久化任務調度信息,無狀態的JobDataMap僅會在Scheduler注冊任務時保持一次,而有狀態任務對應的JobDataMap在每次執行任務后都會進行保存。Trigger自身也可以擁有一個JobDataMap,其關聯的Job可以通過JobExecutionContext#getTrigger().getJobDataMap()獲取Trigger中的JobDataMap。不管是有狀態還是無狀態的任務,在任務執行期間對Trigger的JobDataMap所做的更改都不會進行持久,也即不會對下次的執行產生影響。Quartz擁有完善的事件和監聽體系,大部分組件都擁有事件,如任務執行前事件、任務執行后事件、觸發器觸發前事件、觸發后事件、調度器開始事件、關閉事件等等,可以注冊相應的監聽器處理感興趣的事件。

下圖描述了Scheduler的內部組件結構,SchedulerContext提供Scheduler全局可見的上下文信息,每一個任務都對應一個JobDataMap,虛線表達的JobDataMap表示對應有狀態的任務:


廢話不多說, 上代碼:

  1. 最簡單的Job代碼(就打印Hello Quartz !):
package com.ruixunyun.www.quartz;  
   
import org.quartz.Job;  
import org.quartz.JobExecutionContext;  
import org.quartz.JobExecutionException;  
   
public class HelloQuartz  implements Job {  
   
    public void execute(JobExecutionContext arg0) throws JobExecutionException {  
        System.out.println("Hello Quartz !");                 
    }         
}  
  1. 設置觸發器
package com.ruixunyun.www.quartz;
    
import org.quartz.CronScheduleBuilder;    
import org.quartz.JobBuilder;    
import org.quartz.JobDetail;    
import org.quartz.Scheduler;    
import org.quartz.SchedulerException;  
import org.quartz.SchedulerFactory;    
import org.quartz.SimpleScheduleBuilder;  
import org.quartz.Trigger;    
import org.quartz.TriggerBuilder;    
import org.quartz.impl.StdSchedulerFactory;    
    
public class QuartzStartListener {    
   public static void main(String[] args) throws InterruptedException {    
       
       //通過schedulerFactory獲取一個調度器    
       SchedulerFactory schedulerfactory = new StdSchedulerFactory();    
       Scheduler scheduler=null;    
       try{    
           // 通過schedulerFactory獲取一個調度器    
           scheduler = schedulerfactory.getScheduler();    
               
            // 創建jobDetail實例,綁定Job實現類    
            // 指明job的名稱,所在組的名稱,以及綁定job類    
           JobDetail job = JobBuilder.newJob(HelloQuartz.class).withIdentity("JobName", "JobGroupName").build();    
             
               
            // 定義調度觸發規則    
                           
            // SimpleTrigger   
//      Trigger trigger=TriggerBuilder.newTrigger().withIdentity("SimpleTrigger", "SimpleTriggerGroup")    
//                    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3).withRepeatCount(6))    
//                    .startNow().build();    
             
            //  corn表達式  每五秒執行一次  
              Trigger trigger=TriggerBuilder.newTrigger().withIdentity("CronTrigger1", "CronTriggerGroup")    
              .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))    
              .startNow().build();     
               
            // 把作業和觸發器注冊到任務調度中    
           scheduler.scheduleJob(job, trigger);    
               
           // 啟動調度    
           scheduler.start();    
             
           Thread.sleep(10000);  
             
           // 停止調度  
           scheduler.shutdown();  
               
       }catch(SchedulerException e){    
           e.printStackTrace();    
       }    
           
   }    
}    

輸出(設置了sleep10秒, 故在0秒調度一次, 5秒一次, 10秒最后一次):

Spring中使用Quartz

maven中加入quartz的jar包

<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.1</version>
 </dependency>
  1. web.xml中添加監聽器,監聽QuartzStartListener.class
    備注:放到spring中, SchedulerTest.class去掉了main方法,并且繼承了ServletContextListener類,實現了他的兩個方法:contextInitialized , contextDestroyed
package com.ruixunyun.www.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class QuartzStartListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //通過schedulerFactory獲取一個調度器
        SchedulerFactory schedulerfactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            // 通過schedulerFactory獲取一個調度器
            scheduler = schedulerfactory.getScheduler();

            // 創建jobDetail實例,綁定Job實現類
            // 指明job的名稱,所在組的名稱,以及綁定job類
            JobDetail job = JobBuilder.newJob(HelloQuartz.class).withIdentity("JobName", "JobGroupName").build();

            // 定義調度觸發規則  corn表達式  一分鐘執行一次
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("CronTrigger1", "CronTriggerGroup")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0 */1 * * * ?"))
                    .startNow().build();

            // 把作業和觸發器注冊到任務調度中
            scheduler.scheduleJob(job, trigger);
            // 啟動調度
            scheduler.start();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

web.xml的監聽配置

我的

<listener>
        <listener-class>com.ruixunyun.www.quartz.QuartzStartListener</listener-class>
</listener>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容