Spring Quartz 實現原理與示例


JAVA 針對定時任務,有 Timer,Scheduler, Quartz 等幾種實現方式,其中最常用的應該就是 Quartz 了。

本文旨在簡要介紹 Quartz 的基本概念,在xml中添加定時器,以及使用JAVA代碼動態地修改定時器配置。並附帶了源代碼,可以下載並運行。

1. Quartz的基本概念

在開始之前,我們必須了解以下的幾個基本概念:Job、Trigger、Cron expression、JobStores

1.1. Job

對於任務內容的構建,我們需要創建 Job 的繼承類,並實現 execute 方法。

1.2. Trigger

其作用是設置調度策略。Quartz 設計了多種類型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。
SimpleTrigger 適用於在某一特定的時間以某一特定時間間隔執行多次。

CronTrigger 的用途更廣,主要適用於基於日歷的調度安排。例如:每星期二的 16:38:10 執行,每月一號執行,等等。本文使用的是CronTrigger。

1.3. Cron表達式

由七個字段組成:

Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (Optional field)

舉例如下:

創建一個每三小時執行的 CronTrigger,且從每小時的整點開始執行:

0 0 0/3 * * ?

這里不打算對 Cron 表達式作過多的闡述,更多的內容請查看Oracle官方文檔:

http://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm

對於初學者,推薦使用在線的Cron生成器:
http://cron.qqe2.com

1.4. JobStores

負責對Quartz的持久化,即將任務調度的相關數據保存在數據庫中。這樣,在系統重啟后,任務的調度狀態依然存在系統中。當任務錯過了觸發時間時,還可以重新觸發並執行(需要配置)。

好了,基礎概念介紹完了以后,我們開始搭建一個工程來完成定時任務吧。

 

2. 新建 Quartz 工程

我們會新建一個 Maven 工程,並在啟動Web應用之后運行定時任務。

2.1. 新建工程

在 Eclipse 中,新建 Maven 項目並引入 Jar 包

工程的相關細節,大家請直接參考源碼。

2.2. 版本信息

Java 1.8
Tomcat 8.5
Spring 4.3.3.RELEASE
Quartz 2.2.1

 

 

 

 

2.3. 工程結構

2.4. Spring Quartz 的配置

主要是定義了數據源,調度工廠以及定時任務的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.3.xsd"
            default-autowire="no" >
    
    <!-- 讀取屬性文件 -->
    <context:property-placeholder location="classpath*:properties/quartz.properties" 
        ignore-unresolvable="true" />
    
    <!-- 掃描包 -->
    <context:component-scan base-package="org.stevexie" />
    
    <!-- 數據源 -->
    <bean id="quartzDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName">
            <value>${jdbc.driverclass}</value>
        </property>
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
    </bean>
    
    <!-- ======================== 調度工廠開始 ======================== -->
    <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" autowire="no">
        <!-- 設置持久化的數據源 -->
        <property name="dataSource">
            <ref bean="quartzDataSource" />
        </property>
        
        <!-- 設置Quartz的屬性 -->
        <property name="quartzProperties">
            <props>
                <!-- 線程池配置 -->
                <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
                <!-- 初始化線程數為20 -->
                <prop key="org.quartz.threadPool.threadCount">20</prop>
                <!-- 設置作業的優先級是5 -->
                <prop key="org.quartz.threadPool.threadPriority">5</prop>
                <!-- 初始化線程數為20 -->
                <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
                
                <!-- JobStore 配置,通過數據庫存儲最終調度程序的配置 -->
                <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
                <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
                
                <!-- 數據表名的前綴設置 -->
                <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
            </props>
        </property>
        
        <!-- 設置應用初始化之后,延遲30秒再啟動scheduler -->
        <property name="startupDelay" value="30" />
        
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        
        <!-- 設置定時任務隨web啟動 -->
        <property name="autoStartup" value="true" />
        
        <property name="triggers">
            <list>
                <ref bean="definedInXMLcronTrigger"/>
            </list>
        </property>
    </bean>
    <!-- ======================== 調度工廠結束 ======================== -->
    
    <!-- ======================== 定時任務1 開始 ======================== -->
    <bean id="definedInXMLJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <!-- 必填項:在此處定義job detail -->
        <property name="jobClass" value="org.stevexie.jobdetail.DefinedInXMLJobDetail" />
        <!-- 必填項:在此處定義job name -->
        <property name="name" value="jobName1"></property>
        <!-- 必填項:在此處定義job group name -->
        <property name="group" value="jobGroupName1"></property>
        <!-- 選填項:設置該job是持久性的 -->
        <property name="durability" value="true" />
        <!-- 選填項:設置該job是中斷后可恢復的 -->
        <property name="requestsRecovery" value="true" />
    </bean>
    
    <bean id="definedInXMLcronTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <!-- 必填項:在此處定義trigger name -->
        <property name="name" value="triggerName1" />
        <!-- 必填項:在此處定義trigger group name -->
        <property name="group" value="triggerGroupName1" />
        <property name="jobDetail" ref="definedInXMLJobDetail" />
        <!-- 必填項:在此處定義定時任務時間 -->
        <property name="cronExpression" value="0/10 * * * * ? " />
        <!-- 選填項:在此處定義市區 -->
        <property name="timeZone" ref="timeZone" />
        <property name="misfireInstruction" value="1" />
    </bean>
    <!-- ======================== 定時任務1 結束 ======================== -->
    
    <!-- 時區 -->
    <bean id="timeZone" class="java.util.TimeZone" factory-method="getTimeZone">
        <constructor-arg value="GMT+08:00" />
    </bean>
</beans>
View Code

2.5. 定時任務

實現兩個定時任務:一個是在應用初始化的時候啟動;另一個是通過運行代碼中的main方法來實現對定時任務的增刪改。

2.5.1. 定時任務1

隨着應用的啟動而實例化,每10秒打印一條語句:

 1 package org.stevexie.jobdetail;
 2 
 3 import org.quartz.JobExecutionContext;
 4 import org.springframework.scheduling.quartz.QuartzJobBean;
 5 import org.stevexie.util.DateUtil;
 6 
 7 public class DefinedInXMLJobDetail extends QuartzJobBean {
 8     
 9     @Override
10     protected void executeInternal(JobExecutionContext context) {
11         System.out.println("執行在XML中定義好的定時任務,當前時間是:" + 
12                                         DateUtil.currentDatetime());
13     }
14 }
View Code

2.5.2. 定時任務2

與定時任務1的內容基本一致,但主要是在 JAVA 代碼中被實例化並實現定時任務的增刪改。

 1 package org.stevexie.jobdetail;
 2 
 3 import org.quartz.JobExecutionContext;
 4 import org.springframework.scheduling.quartz.QuartzJobBean;
 5 import org.stevexie.util.DateUtil;
 6 
 7 public class DefinedInCodeJobDetail extends QuartzJobBean {
 8     
 9     @Override
10     protected void executeInternal(JobExecutionContext context) {
11         System.out.println("執行在代碼中添加的定時任務,當前時間是:" + 
12                                     DateUtil.currentDatetime());
13     }
14 }
View Code

2.6. 定時任務的增刪改工具類

  1 package org.stevexie.util;
  2 
  3 import java.text.ParseException;
  4 
  5 import javax.annotation.Resource;
  6 
  7 import org.apache.commons.lang.StringUtils;
  8 import org.quartz.CronScheduleBuilder;
  9 import org.quartz.CronTrigger;
 10 import org.quartz.Job;
 11 import org.quartz.JobBuilder;
 12 import org.quartz.JobDetail;
 13 import org.quartz.JobKey;
 14 import org.quartz.Scheduler;
 15 import org.quartz.SchedulerException;
 16 import org.quartz.Trigger;
 17 import org.quartz.TriggerBuilder;
 18 import org.quartz.TriggerKey;
 19 import org.springframework.context.ApplicationContext;
 20 import org.springframework.context.support.ClassPathXmlApplicationContext;
 21 import org.springframework.stereotype.Component;
 22 import org.stevexie.jobdetail.DefinedInCodeJobDetail;
 23 import org.stevexie.jobdetail.DefinedInXMLJobDetail;
 24 
 25 @Component("quartzUtil")
 26 public class QuartzUtil {
 27     
 28     @Resource(name="quartzScheduler")
 29     private Scheduler scheduler;
 30     
 31     private static String JOB_GROUP_NAME = "ddlib";
 32     private static String TRIGGER_GROUP_NAME = "ddlibTrigger";
 33     
 34     /**
 35      * 添加一個定時任務,使用默認的任務組名,觸發器名,觸發器組名
 36      * @param job Job任務類實例
 37      * @param jobName job名字(請保證唯一性)
 38      * @param cronExpression cron時間表達式
 39      * @throws SchedulerException 
 40      */
 41     public void addJob(String jobName, Job job, String cronExpression)
 42             throws SchedulerException, ParseException{
 43         addJob(job, jobName, null, jobName, null, cronExpression, 5);
 44     }
 45     
 46     /**
 47      * 開始一個simpleSchedule()調度(創建一個新的定時任務)
 48      * @param job Job任務類實例
 49      * @param jobName  job名字(請保證唯一性)
 50      * @param jobGroupName  job組名(請保證唯一性)
 51      * @param cronExpression    cron時間表達式
 52      * @param triggerName   trigger名字(請保證唯一性)
 53      * @param triggerGroupName  triggerjob組名(請保證唯一性)
 54      * @throws SchedulerException 
 55      */
 56     public void addJob(Job job, String jobName, String jobGroupName, 
 57                                 String triggerName, String triggerGroupName, 
 58                                 String cronExpression, int triggerPriority) 
 59                                                 throws SchedulerException {
 60         
 61         if(StringUtils.isEmpty(jobGroupName)){
 62             jobGroupName = JOB_GROUP_NAME;
 63         }
 64         if(StringUtils.isEmpty(triggerGroupName)){
 65             triggerGroupName = TRIGGER_GROUP_NAME;
 66         }
 67         
 68         // 1、創建一個JobDetail實例,指定Quartz
 69         JobDetail jobDetail = JobBuilder.newJob(job.getClass())
 70                 // 任務執行類
 71                 .withIdentity(jobName, jobGroupName)
 72                 // 任務名,任務組
 73                 .build();
 74         
 75         // 2、創建Trigger
 76         // 優先級默認是5,數字越高優先級越高
 77         Trigger trigger = TriggerBuilder.newTrigger()
 78                 .withIdentity(triggerName, triggerGroupName).startNow()
 79                 .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
 80                 .withPriority(triggerPriority)
 81                 .build();
 82         
 83         // 3、移除job,避免因為job的重復添加導致錯誤
 84         this.removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
 85         
 86         // 4、調度執行
 87         try {
 88             scheduler.scheduleJob(jobDetail, trigger);
 89         } catch (SchedulerException e) {
 90             e.printStackTrace();
 91             throw e;
 92         }
 93         
 94         // 4、啟動  
 95         this.startSchedule();
 96     }
 97     
 98     /**
 99      * 開始任務
100      * @throws SchedulerException 
101      */
102     public void startSchedule() throws SchedulerException {
103         try {
104             if(scheduler.isShutdown()){
105                 scheduler.resumeAll();
106             } else {
107                 scheduler.start();
108             }
109         } catch (SchedulerException e) {
110             e.printStackTrace();
111             throw e;
112         }
113     }
114     
115     /**
116      * 暫停Job
117      * @param name job名字
118      * @param group  job組名
119      * @throws SchedulerException 
120      */
121     public void pauseJob(String jobName, String jobGroupName) 
122                                     throws SchedulerException {
123         JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
124         try {
125             scheduler.pauseJob(jobKey);
126         } catch (SchedulerException e) {
127             e.printStackTrace();
128             throw e;
129         }
130     }
131     
132     /**
133      * 恢復Job
134      * @param name  job名字
135      * @param group  job組名
136      * @throws SchedulerException 
137      */
138     public void resumeJob(String jobName, String jobGroupName) 
139                                     throws SchedulerException {
140         JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
141         try {
142             scheduler.resumeJob(jobKey);
143         } catch (SchedulerException e) {
144             e.printStackTrace();
145             throw e;
146         }
147     }
148     
149     /**  
150      * 修改一個任務的觸發時間(使用默認的任務組名,觸發器名,觸發器組名) 
151      */  
152     public void modifyJobTime(String jobName, String cronExpression)
153             throws SchedulerException, ParseException {
154         rescheduleJob(jobName, null, cronExpression);
155     }
156     
157     /**
158      * 更新任務表達式
159      * @param triggerName  trigger名字
160      * @param triggerGroupName  trigger組名
161      * @param newCronExpression  cron時間表達式
162      * @throws SchedulerException 
163      */
164     public void rescheduleJob(    String triggerName, 
165                                 String triggerGroupName, 
166                                 String newCronExpression) 
167                                 throws SchedulerException {
168         
169         if(StringUtils.isBlank(triggerGroupName)) {
170             triggerGroupName = TRIGGER_GROUP_NAME;
171         }
172         
173         TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
174         
175         // 獲取trigger
176         CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
177         
178         // 按新的cronExpression表達式重新構建trigger
179         trigger = trigger
180                 .getTriggerBuilder()
181                 .withIdentity(triggerKey)
182                 .withSchedule(CronScheduleBuilder.cronSchedule(newCronExpression))
183                 .build();
184         
185         // 按新的trigger重新設置job執行
186         try {
187             scheduler.rescheduleJob(triggerKey, trigger);
188         } catch (SchedulerException e) {
189             e.printStackTrace();
190             throw e;
191         }
192     }
193     
194     /** 移除一個任務和觸發器(使用默認的任務組名,觸發器名,觸發器組名) */
195     public void removeJob(String jobName,String triggerName)
196             throws SchedulerException{
197         removeJob(jobName, null, triggerName, null);
198     }
199     
200     /** 移除一個任務和觸發器  */
201     public void removeJob(String jobName, String jobGroupName, 
202             String triggerName, String triggerGroupName)
203                     throws SchedulerException {
204         
205         if(StringUtils.isEmpty(jobGroupName)) {
206             jobGroupName = JOB_GROUP_NAME;
207         }
208         if(StringUtils.isEmpty(triggerGroupName)) {
209             triggerGroupName = TRIGGER_GROUP_NAME;
210         }
211         
212         JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
213         TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
214         
215         scheduler.pauseTrigger(triggerKey);            // 停止觸發器
216         scheduler.unscheduleJob(triggerKey);        // 移除觸發器
217         scheduler.deleteJob(jobKey);                // 刪除任務
218     }
219     
220     public static void main(String[] args) {
221         ApplicationContext context = null;
222         context = new ClassPathXmlApplicationContext("classpath:spring/spring-quartz.xml");
223         
224         QuartzUtil quartzUtil = (QuartzUtil) context.getBean("quartzUtil");
225         
226         // 刪除定時器
227         try {
228             quartzUtil.removeJob(   "jobName", 
229                                     "jobGroupName", 
230                                     "triggerName", 
231                                     "triggerGroupName");
232             quartzUtil.removeJob(   "jobName1", 
233                                     "jobGroupName", 
234                                     "triggerName", 
235                                     "triggerGroupName");
236             quartzUtil.removeJob(   "jobName2", 
237                                     "jobGroupName", 
238                                     "triggerName", 
239                                     "triggerGroupName");
240         } catch (SchedulerException e1) {
241             e1.printStackTrace();
242         }
243         
244         // 添加定時器
245         try {
246             quartzUtil.addJob(new DefinedInXMLJobDetail(), 
247                     "jobName1", "jobGroupName1", 
248                     "triggerName1", "triggerGroupName1", 
249                     "0/20 * * * * ? ", 5);
250             
251             quartzUtil.addJob(new DefinedInCodeJobDetail(), 
252                     "jobName2", "jobGroupName2", 
253                     "triggerName2", "triggerGroupName2", 
254                     "0/10 * * * * ? ", 5);
255             
256         } catch (SchedulerException e2) {
257             e2.printStackTrace();
258         }
259         
260         // 修改定時器
261         try {
262             quartzUtil.rescheduleJob(   "triggerName1", 
263                                         "triggerGroupName1", 
264                                         "0/30 * * * * ? ");
265         } catch (SchedulerException e1) {
266             e1.printStackTrace();
267         }
268         
269         try {
270             Thread.sleep(100*1000);
271         } catch (InterruptedException e) {
272             e.printStackTrace();
273         }
274     }
275 }
View Code

2.7. Quartz的執行腳本

需要在數據庫中建一些Quartz的表。Quartz 的 distribution 包已提供了建表語句,放在\docs\dbTables目錄中。下載地址是:

http://www.quartz-scheduler.org/downloads/

本文附帶的源碼中也包含了定時器的 SQL 執行腳本。

 

3. 運行效果

3.1. 初次運行web項目

每十秒鍾會執行一次定時任務。該任務是在XML中被定義的,隨着Web應用的啟動而實例化。

3.2. 修改定時任務的屬性

停止 Web 項目,運行 QuartzUtil.java。結果顯示,目前有兩個定時任務正在運行:

    - 定時任務一,每隔30秒運行一次,並打印:執行在XML中定義好的定時任務...

    - 定時任務二,每隔10秒運行一次,並打印:執行在代碼中添加的定時任務...

3.3. 再次運行 Web 項目,確認修改結果

執行結果與 3.2 一致,說明定時任務的觸發時間已經成功被修改了:

 

3.4. 在數據庫中,查詢定時器信息,確認該調度程序已被成功地持久化。


4. 源代碼下載

請移步百度網盤,下載源代碼:

https://pan.baidu.com/s/1boRrS1T

 


免責聲明!

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



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