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