目录:
1. SpringBoot集成Quartz
2. 实现定时任务逻辑
3. 动态创建定时任务
4. Quartz常用类
5. Quartz任务熄火策略
6. Cron表达式速成
7. Quartz使用技巧之通过JobDataMap和SchedulerContext传递数据
8. Spring框架Schedule功能的使用方法
Quartz是开源组织OpenSymphony的一个作业调度框架,采用多线程架构,可伸缩性强,可集群扩展。SpringBoot集成Quartz简单高效,只需实现Job接口,在方法execute()中添加业务逻辑。
Spring框架自带了Schedule功能,能满足小型项目对定时任务的功能需求,同样支持Cron表达式、固定间隔、固定频率三种配置方式,但和Quartz相比,不支持任务信息数据库持久化。
Cron表达式 | 固定间隔 | 固定频率 | 传递数据 | 任务持久化 | |
Quartz | √ | √ | √ | √ | √ |
Spring Schedule | √ | √ | √ | × | × |
本文分享SpringBoot集成和配置Quartz的方法,以及在项目中的实际应用方案,最后介绍Spring 框架自带的Schedule功能的用法。
代码文件 | 功能要点 | |
SpringBoot集成Quartz | pom.xml | 引入Quartz依赖:spring-boot-starter-quartz |
application.yml | 配置Quartz属性,配置Job运行时间cron表达式 | |
QuartzConfig.java | 配置Bean: JobDetail, Trigger,读取cron运行时间配置 | |
实现定时任务 | QuartzJob.java | 实现Job接口,或者继承QuartzJobBean类 |
动态创建任务 | CheckController.java | 增加REST接口/chk/job,创建一个Job定时任务,通过Context上下文传递数据。 |
项目代码:https://github.com/jextop/StarterApi/
示例代码:https://github.com/rickding/HelloJava/tree/master/HelloQuartz
一,SpringBoot集成Quartz
1. 新建SpringBoot项目时,选中Quartz,将自动添加Quartz依赖。
2. 已有SpringBoot项目,可以在pom.xml中直接添加Quartz依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
3. 在application.yml中配置Quartz,指定任务存储类型:
memory:内存方式,默认。
jdbc:数据库持久化方式,将创建数据表并保存任务信息。
spring:
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: always
job:
quartz:
cron: 0 0/23 * * * ?
4. 在QuartzConfig.java中配置Bean,声明JobDetail和Trigger,使用cron表达式设置任务运行时间:
@Configuration
@ConfigurationProperties("job.quartz")
public class QuartzConfig {
private String cron;
@Bean
public JobDetail quartzJob() {
return JobBuilder.newJob(QuartzJob.class).storeDurably().build();
}
@Bean
public Trigger quartzTrigger() {
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
return TriggerBuilder.newTrigger()
.forJob(quartzJob())
.withSchedule(scheduleBuilder)
.build();
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
}
二,定时任务QuartzJob.java,继承QuartzJobBean类,实现Job接口。
从JobExecutionContext中读取附加信息,执行业务逻辑。
public class QuartzJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
LogUtil.info("quartz job ", new Date());
try {
JextService jextService = (JextService) context.getScheduler().getContext().get("jextService");
if (jextService != null) {
jextService.getInfo(true);
}
} catch (SchedulerException e) {
LogUtil.info(e.getMessage());
}
}
}
三,动态创建定时任务
1. 增加RestController:CheckController.java
2. 增加REST接口/chk/job,创建一个定时任务,添加附加信息到Scheduler上下文中。
@GetMapping(value = "/chk/job")
public Object job() {
JobDetail job = JobBuilder.newJob(QuartzJob.class).build();
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.forJob(job)
.startAt(new Date())
.build();
Date date = null;
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.getContext().put("jextService", jextService);
date = scheduler.scheduleJob(job, trigger);
scheduler.startDelayed(1);
} catch (SchedulerException e) {
e.printStackTrace();
}
final Date jobDate = date;
return new HashMap<String, Object>() {{
put("chk", "job");
put("date", jobDate);
}};
}
3. Postman调用REST接口创建定时任务示例
四,Quartz常用类
Quartz提供的常用类:Scheduler, SchedulerFactory, Job, JobDetail, JobBuilder, Trigger, TriggerBuilder, ScheduleBuilder,UML类图如下:
Scheduler时Quartz调度程序的主要接口,维护一个JobDetails和Triggers的注册表,到触发时间时,调度程序将执行Job。
调度程序Scheduler实例通过SchedulerFactory工厂创建,有两个实现类DirectSchedulerFactory和StdSchedulerFactory,前者不写入持久化数据库,后者加载quartz.properties属性配置文件,将查找加载当前目录和org.quartz包。
任务是实现Job接口的一个类,实现方法execute(),可声明属性:
- @DisallowConcurrentExecution,同时只执行一个实例。
- @PersisJobDataAfterExecution,正常执行后将JobDataMap备份。
JobDetail将任务属性传递给Scheduler,通过JobBuilder创建,JobDataMap保存任务实例的状态信息。
触发器Trigger通过TriggerBuilder创建,结合ScheduleBuilder设置时间规则,可通过JobDataMap传递数据给Job。常用的两种触发器:
- SimpleTrigger:指定开始时间、结束时间、重复次数、执行间隔。
- CronTrigger:使用Cron表达式设置时间规则。
五,Quartz任务熄火策略
ScheduleBuilder设置时间规则时,可配置Misfire选项,指定执行失败熄火时的处理规则:
含义 | 支持ScheduleBuilder | |
IgnoreMisfires | 马上执行,比如整点9点失败,系统10:15启动,会马上执行9点10点的任务。 | SimpleSchedule CronScheduleBuilder CalendarIntervalScheduleBuilder DailyTimeIntervalScheduleBuilder |
FireNow | 立即再次触发 | SimpleSchedule |
NowWithExistingCount | 立即再次触发,不计入总次数。 | SimpleSchedule |
NowWithRemainingCount | 立即再次触发,计入总次数。 | SimpleSchedule |
NextWithExistingCount | 等待下次执行,不计入总次数。 | SimpleSchedule |
NextWithRemainingCount | 等待下次执行,计入总次数。 | SimpleSchedule |
DoNothing | 不做任何处理,执行下一次周期。 | CronScheduleBuilder CalendarIntervalScheduleBuilder DailyTimeIntervalScheduleBuilder |
FireAndProceed | 合并下一个周期,正常执行。比如整点9点10点失败,系统10:15启动,在11点合并执行一次。 | CronScheduleBuilder CalendarIntervalScheduleBuilder DailyTimeIntervalScheduleBuilder |
六,Cron表达式速成
Cron表达式是一个字符串,定义时间规则,由6或7个时间域组成,空格分隔1张表整理清楚含义和规则,并举例常用表达式,放手边速查。
时间域序号 | 含义 | 取值范围 | 特殊字符 |
1 | 秒Seconds | 0-59 | ,-*/ |
2 | 分钟Minutes | 0-59 | ,-*/ |
3 | 小时Hours | 0-23 | ,-*/ |
4 | 日期DayOfMonth | 1-31 | ,-*/ ? L W C |
5 | 月份Month | 1-12 | ,-*/ JAN-DEC |
6 | 星期DayOfWeek | 1-7 | ,-*/ ? L C # SUN-SAT |
7 | 年Year (可选) | 1970-2099 | ,-*/ |
特殊字符含义
JAN-DEC 月份英语简称
SUN-SAT 星期英语简称
星期的1表示星期天,2表示星期一,依次类推
* 表示取值范围内的所有数字
/ 表示每隔固定时间触发依次,比如0/5表示从0开始每5个单位时间
- 表示两个数字之间的范围,比如3-7表示3到7之间,包含3和7
, 表示离散的枚举数字,比如2,3,5,7表示指定的这几个时间
? 只能用在日期DayOfMonth和星期DayOfWeek两个域,表示不指定,避免日期和星期的互相影响,比如指定每月的20日,不管是星期几,正确写法是:0 0 0 20 * ?,其中星期只能用?,如果使用*将触发错误。
L 只能用于日期DayOfMonth和星期DayOfWeek,用于日期时表示月份的最后一天,用于星期时不加数字表示周六,加数字表示最后一个周几,比如0 0 0 ? * 5L表示每月的最后一个星期四
W 只能用于日期DayOfMonth,表示周一到周五有效工作日,将在离指定日期的最近的有效工作日触发事件。例如在日期使用5W,如果5日是星期六,则将在最近的工作日星期五(4日)触发。如果5日是星期天,则在6日(星期一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近工作日寻找不会跨月份。
LW 两个字符连用时表示某个月最后一个工作日
# 只能用于星期DayOfWeek,表示每个月第几个星期几,比如4#2表示第二个星期三
常用表达式
0/5 * * * * ? 每5秒钟
0 0/5 * * * ? 每5分钟
0 0 6 * * ? 每天早上6点
0 0 9,13,19 * * ? 每天上午9点,下午1点,晚上7点
0 0 23-7/2,8 * * ? 每天晚上11点到早上7点之间的每两个小时,和早上8点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 9-21 ? * MON-SAT 表示996每天的每小时
0 0 7 LW * ? 每月最后一个工作日早上7点
0 0 4 1 1 ? 每年的1月1日早上4点
七,Quartz使用技巧之通过JobDataMap和SchedulerContext传递数据
在Job.execute()方法中实现业务逻辑时,经常需要一些附加信息。Quartz提供了JobExecutionContext上下文传递数据。
l 通过JobDataMap传递数据
代码下载:https://github.com/rickding/HelloJava/tree/master/HelloQuartz
src/main/java/com/hello/quartz/
├── QuartzConfig.java
├── QuartzJob.java
代码文件 | 功能要点 | |
设置数据 | QuartzConfig.java | 创建JobDetail或者Trigger时,调用usingJobData()设置数据 |
读取数据 | QuartzJob.java | 执行任务时,调用JobExecutionContext.getMergedJobDataMap()获取数据 |
1,设置数据:JobDetail和Trigger都可以调用usingJobData()方法设置数据。
@Configuration
@ConfigurationProperties("quartz")
public class QuartzConfig {
@Bean
public JobDetail quartzJob() {
JobDataMap dataMap = new JobDataMap() {{
put("job_str", "str_test");
}};
return JobBuilder.newJob(QuartzJob.class)
.usingJobData(dataMap)
.storeDurably()
.build();
}
@Bean
public Trigger quartzTrigger() {
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
JobDataMap dataMap = new JobDataMap() {{
put("trigger_int", 333);
}};
return TriggerBuilder.newTrigger()
.forJob(quartzJob())
.withSchedule(scheduleBuilder)
.usingJobData(dataMap)
.build();
}
}
2,读取数据:从JobExecutionContext中读取JobDataMap获取数据,执行业务逻辑。
public class QuartzJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// get data from context
JobDataMap dataMap = context.getMergedJobDataMap();
for (Map.Entry<String, Object> data : dataMap.entrySet()) {
System.out.printf("%s = %s\n", data.getKey(), data.getValue());
}
// do work
}
}
3,运行输出:
trigger_int = 333
job_str = str_test
l 通过SchedulerContext传递数据
另一个传递数据的方式是构建Scheduler时为SchedulerContext设置数据。代码详见上文中第二、第三部分:
代码下载:https://github.com/jextop/StarterApi/
src/main/java/com/starter/
├── controller/CheckController.java
├── job/QuartzJob.java
读取数据方式一样,因为JobExecutionContext.getMergedJobDataMap()已经将数据合并到一起。也可以通过调用SchedulerContext获取。
八,Spring框架Schedule功能的使用方法
Spring Schedule支持异步执行定时任务,支持Cron表达式、固定间隔、固定频率三种配置方式,能满足小型项目对定时任务的功能需求。示例代码:https://github.com/rickding/HelloJava/tree/master/HelloQuartz
src/main/java/com/hello/quartz/
├── QuartzApplication.java
├── ScheduledJob.java
代码文件 | 功能要点 | |
开启功能 | QuartzApplication.java | SpringApplication增加注解: @EnableScheduling // 打开Schedule功能 @EnableAsysnc // 异步方式执行定时任务 |
配置任务 | ScheduleJob.java | 声明定时任务,功能注解: @Async // 异步方式执行,声明在类上面时,作用于类里面包含的任务 @Scheduled // 声明定时任务执行时间 |
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class QuartzApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzApplication.class, args);
}
}
1,cron表达式声明定时任务,比如每10秒执行一次:
@Async
@Scheduled(cron = "0/10 * * * * ?")
public void scheduledCron() {
System.out.printf("scheduled cron: %s\n", new Date());
}
2,固定频率执行定时任务,比如每20秒执行一次:
@Scheduled(fixedRate = 1000 * 20, initialDelay = 1000 * 20)
public void scheduledRate() {
System.out.printf("fixedRate: %s\n", new Date());
}
3,固定间隔执行定时任务,比如每20秒执行一次:
@Scheduled(fixedDelay = 1000 * 30, initialDelay = 1000 * 30)
public void scheduledDelay() {
System.out.printf("fixedDelay: %s\n", new Date());
}
注意固定频率和固定间隔两类定时任务,默认在程序启动时就会执行一次,可以通过指定initialDelay参数控制首次执行时间。
scheduled cron: Sun Feb 09 18:09:19 CST 2020
fixedRate: Sun Feb 09 18:09:28 CST 2020
scheduled cron: Sun Feb 09 18:09:38 CST 2020
--------------------------------
如果您觉得这篇文章对您有帮助,请点个“赞”,博主感激不尽!
Jext技术社区专注领域:软件工程实践,JIRA研发管理,分布式系统架构,软件质量保障。