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>
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 }
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 }
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 }
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