Quartz基礎+實例


1. 介紹

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表達式的格式:秒 分 時 日 月 周 年(可選)。
               字段名                 允許的值                        允許的特殊字符  
               秒                         0-59                               , - * /  
               分                         0-59                               , - * /  
               小時                   0-23                                 , - * /  
               日                         1-31                               , - * ? / L W C  
               月                         1-12 or JAN-DEC           , - * /  
               周幾                     1-7 or SUN-SAT             , - * ? / L C #      SUN, MON, TUE, WED, THU, FRI and SAT
               年 (可選字段)     empty, 1970-2099            , - * /


               “?”字符:表示不確定的值
               “,”字符:指定數個值
               “-”字符:指定一個值的范圍
               “/”字符:指定一個值的增加幅度。n/m表示從n開始,每次增加m
               “L”字符:用在日表示一個月中的最后一天,用在周表示該月最后一個星期X

               “W”字符:指定離給定日期最近的工作日(周一到周五)
               “#”字符:表示該月第幾個周X。6#3表示該月第3個周五

         Cron表達式范例:
                 每隔5秒執行一次:*/5 * * * * ?
                 每隔1分鍾執行一次:0 */1 * * * ?
                 每天23點執行一次:0 0 23 * * ?
                 每天凌晨1點執行一次:0 0 1 * * ?
                 每月1號凌晨1點執行一次:0 0 1 1 * ?
                 每月最后一天23點執行一次:0 0 23 L * ?
                 每周星期六凌晨1點實行一次:0 0 1 ? * L
                 在26分、29分、33分執行一次:0 26,29,33 * * * ?
                 每天的0點、13點、18點、21點都執行一次:0 0 0,13,18,21 * * ?

4. Calendarorg.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分別針對每年、每月和每周進行定義;

5. 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實例;

6. 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表示對應有狀態的任務:

2. 創建簡單的定時任務

使用Quartz需要導入jar包:

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.2.3</version>
</dependency>

2.1創建工作類

創建一個工作類,需要實現Job接口

  public class HelloQuartz implements Job {
      //執行
      public void execute(JobExecutionContext context) throws JobExecutionException {
          //創建工作詳情
          JobDetail detail=context.getJobDetail();
          //獲取工作的名稱
          String name=detail.getJobDataMap().getString("name");
          String job=detail.getKey().getGroup();
          System.out.println("任務調度:組:"+job+",工作名:"+name+"---->今日整點搶購,不容錯過!");
     }
 }

2.2測試任務調度

  public class QuartzTest {
      public static void main(String[] args) {
          try{
              //創建scheduler,執行計划
              Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
              //定義一個Trigger,觸發條件類
              Trigger trigger = TriggerBuilder.newTrigger().
                      withIdentity("trigger1", "group1") //定義name/group
                      .startNow()//一旦加入scheduler,立即生效
                     .withSchedule(SimpleScheduleBuilder.simpleSchedule() //使用SimpleTrigger
                             .withIntervalInSeconds(1) //每隔一秒執行一次
                             .repeatForever()) //一直執行,奔騰到老不停歇
                     .build();
             //定義一個JobDetail
             JobDetail job = JobBuilder.newJob(HelloQuartz.class) //定義Job類為HelloQuartz類,這是真正的執行邏輯所在
                     .withIdentity("job1", "group1") //定義name/group
                     .usingJobData("name", "quartz") //定義屬性
                     .build();
             //加入這個調度
             scheduler.scheduleJob(job, trigger);
             //啟動任務調度
             scheduler.start();
         }catch (Exception ex){
             ex.printStackTrace();
         }
     }
 }

3. 動態操作任務

3.1新建任務類

 public class MyJob implements Job {
     private static int counter = 1;
 
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
         System.out.println("(第 " + counter + " 次,預告通知)");
         counter++;
     }
 }

3.2編寫任務工具類

 public class QuartzManager {
   
       private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();
   
      /**
       * @Description: 添加一個定時任務
       *
       * @param jobName 任務名
       * @param jobGroupName  任務組名
       * @param triggerName 觸發器名
       * @param triggerGroupName 觸發器組名
       * @param jobClass  任務
       * @param cron   時間設置,參考quartz說明文檔
     */
 @SuppressWarnings({ "unchecked", "rawtypes" })
     public static void addJob(String jobName, String jobGroupName,
                               String triggerName, String triggerGroupName, Class jobClass, String cron) {
         try {
             Scheduler sched = schedulerFactory.getScheduler();
             // 任務名,任務組,任務執行類
             JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
             // 觸發器
             TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
             // 觸發器名,觸發器組
             triggerBuilder.withIdentity(triggerName, triggerGroupName);
             triggerBuilder.startNow();
             // 觸發器時間設定
             triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
             // 創建Trigger對象
             CronTrigger trigger = (CronTrigger) triggerBuilder.build();
             // 調度容器設置JobDetail和Trigger
             sched.scheduleJob(jobDetail, trigger);
             // 啟動
             if (!sched.isShutdown()) {
                 sched.start();
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         
     }
 /**
      * @Description: 修改一個任務的觸發時間
      *
      * @param jobName
      * @param jobGroupName
      * @param triggerName 觸發器名
      * @param triggerGroupName 觸發器組名
      * @param cron   時間設置,參考quartz說明文檔
      */
     public static void modifyJobTime(String jobName,
                                      String jobGroupName, String triggerName, String triggerGroupName, String cron) {
         try {
 Scheduler sched = schedulerFactory.getScheduler();
             TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
             CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
             if (trigger == null) {
                 return;
             }
 
             String oldTime = trigger.getCronExpression();
             if (!oldTime.equalsIgnoreCase(cron)) {
                 /** 方式一 :調用 rescheduleJob 開始 */
                 // 觸發器
                 TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                 // 觸發器名,觸發器組
                 triggerBuilder.withIdentity(triggerName, triggerGroupName);
                 triggerBuilder.startNow();
                 // 觸發器時間設定
                 triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
                 // 創建Trigger對象
                 trigger = (CronTrigger) triggerBuilder.build();
                 // 方式一 :修改一個任務的觸發時間
                 sched.rescheduleJob(triggerKey, trigger);
                 /** 方式一 :調用 rescheduleJob 結束 */
                 /** 方式二:先刪除,然后在創建一個新的Job  */
                 //JobDetail jobDetail = sched.getJobDetail(JobKey.jobKey(jobName, jobGroupName));
                 //Class<? extends Job> jobClass = jobDetail.getJobClass();
                 //removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
                 //addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
     /**
      * @Description: 移除一個任務
      *
      * @param jobName
      * @param jobGroupName
      * @param triggerName
      * @param triggerGroupName
  */
     public static void removeJob(String jobName, String jobGroupName,
                                  String triggerName, String triggerGroupName) {
         try {
             Scheduler sched = schedulerFactory.getScheduler();
             TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
             sched.pauseTrigger(triggerKey);// 停止觸發器
             sched.unscheduleJob(triggerKey);// 移除觸發器
             sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 刪除任務
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
     /**
      * @Description:啟動所有定時任務
      */
     public static void startJobs() {
         try {
             Scheduler sched = schedulerFactory.getScheduler();
             sched.start();
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
     /**
      * @Description:關閉所有定時任務
      */
     public static void shutdownJobs() {
         try {
             Scheduler sched = schedulerFactory.getScheduler();
             if (!sched.isShutdown()) {
                 sched.shutdown();
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }

3.3測試類

 public class SimpleTest {
     public static String JOB_NAME = "動態任務調度";
     public static String TRIGGER_NAME = "動態任務觸發器";
     public static String JOB_GROUP_NAME = "XLXXCC_JOB_GROUP";
     public static String TRIGGER_GROUP_NAME = "XLXXCC_JOB_GROUP";
 
     public static void main(String[] args) {
         try {
             System.out.println("【系統啟動】開始(每1秒輸出一次)...");
             QuartzManager.addJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, MyJob.class, "0/1 * * * * ?");
 
             Thread.sleep(5000);
             System.out.println("【修改時間】開始(每5秒輸出一次)...");
             QuartzManager.modifyJobTime(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, "0/5 * * * * ?");
 
             Thread.sleep(6000);
             System.out.println("【移除定時】開始...");
             QuartzManager.removeJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME);
             System.out.println("【移除定時】成功");
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

4. Spring整合Quartz

導入Spring核心包:

 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>4.3.11.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context-support</artifactId>
     <version>4.3.11.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-tx</artifactId>
     <version>4.3.11.RELEASE</version>
 </dependency>

4.1 創建工作類

 public class MyJob implements Job {
     private static int counter = 1;
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
         System.out.println("(第 " + counter + " 次,預告通知)");
         counter++;
     }
 }

4.2Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
     <!--
            Spring整合Quartz進行配置遵循下面的步驟:
            1:定義工作任務的Job
            2:定義觸發器Trigger,並將觸發器與工作任務綁定
            3:定義調度器,並將Trigger注冊到Scheduler
 -->
 
     <!-- 1:定義任務的bean ,這里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置類似-->
     <bean name="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
         <!-- 指定job的名稱 -->
         <property name="name" value="job1"/>
         <!-- 指定job的分組 -->
         <property name="group" value="group1"/>
         <!-- 指定具體的job類 -->
         <property name="jobClass" value="spring.MyJob"/>
         <!-- 必須設置為true,如果為false,當沒有活動的觸發器與之關聯時會在調度器中會刪除該任務  -->
         <property name="durability" value="true"/>
         <!-- 指定spring容器的key,如果不設定在job中的jobmap中是獲取不到spring容器的 -->
         <property name="applicationContextJobDataKey" value="applicationContext"/>
     </bean>
 
     <!-- 2.2:定義觸發器的bean,定義一個Cron的Trigger,一個觸發器只能和一個任務進行綁定 -->
     <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
         <!-- 指定Trigger的名稱 -->
         <property name="name" value="my_trigger"/>
         <!-- 指定Trigger的名稱 -->
         <property name="group" value="my_trigger_group"/>
         <!-- 指定Tirgger綁定的Job -->
         <property name="jobDetail" ref="myJob"/>
         <!-- 指定Cron 的表達式 ,當前是每隔5s運行一次 -->
         <property name="cronExpression" value="0/5 * * * * ?" />
     </bean>
 
     <!-- 3.定義調度器,並將Trigger注冊到調度器中 -->
     <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
         <property name="triggers">
             <list>
                 <ref bean="cronTrigger"/>
             </list>
         </property>
         <property name="autoStartup" value="false" />
     </bean>
 </beans>

4.3測試代碼

  public class SpringTest {
      public static void main(String[] args) throws Exception {
          ApplicationContext applicationContext =
                  new ClassPathXmlApplicationContext("spring-quartz.xml");
          StdScheduler scheduler =
                  (StdScheduler) applicationContext.getBean("scheduler");
          scheduler.start();
          System.in.read();
      }
 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM