Spring Boot + Quartz 实现动态配置定时任务
使用 Spring Boot + Quartz 实现定时任务的动态配置

背景

最近帮别人开发一个数据定时导入的小项目时,需求中明确提出导入数据的时间为可配置式。所以写下这篇文章记录一下。

准备工作

  • 准备mysql数据库,主要存储需要动态配置的cron表达式

  • 执行SQL,需要先创建数据库:springbootdb

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    DROP TABLE IF EXISTS `job_cron`;
    CREATE TABLE `job_cron` (
    `id` int(10) NOT NULL,
    `cron` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    `current` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0',
    `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

    INSERT INTO `job_cron` VALUES (1, '0 0/5 * * * ? ', '0', '每5分钟执行一次');
    INSERT INTO `job_cron` VALUES (2, '0 0/15 * * * ? ', '0', '每15分钟执行一次');
    INSERT INTO `job_cron` VALUES (3, '0 0/30 * * * ? ', '0', '每30分钟执行一次');
    INSERT INTO `job_cron` VALUES (4, '0 0 0/1 * * ? ', '0', '每小时执行一次');
    INSERT INTO `job_cron` VALUES (5, '0 0 0/3 * * ? ', '0', '每3小时执行一次');
    INSERT INTO `job_cron` VALUES (6, '0 0 0/6 * * ? ', '0', '每6小时执行一次');
    INSERT INTO `job_cron` VALUES (7, '0 0 0/12 * * ? ', '0', '每12小时执行一次');
    INSERT INTO `job_cron` VALUES (8, '0 0 8 1/1 * ? ', '0', '每天的早上8点执行一次');
    INSERT INTO `job_cron` VALUES (9, '0 0 8 1/5 * ? ', '0', '每隔五天的早上8点执行一次');
    INSERT INTO `job_cron` VALUES (10, '0/1 * * * * ? ', '1', '每隔1秒执行一次');
    INSERT INTO `job_cron` VALUES (11, '0/5 * * * * ? ', '0', '每隔5秒执行一次');

    SET FOREIGN_KEY_CHECKS = 1;
  • 表结构

不多BB,直接上代码

主要代码

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<dependencies>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Mysql Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- scheduler支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

配置任务详情类以及触发器类

在任务启动时,查询数据库获取初始执行Cron表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.ensat.config;

import com.ensat.entities.mysql.JobCron;
import com.ensat.services.JobCronService;
import com.ensat.services.ScheduleTask;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfiguration {

@Autowired
private JobCronService jobCronService;


/**
* 配置任务详情, 其中大多属性都可以保存在数据库,比如任务名称, 任务组, 任务调用的方法
*
* @param task
* @return
*/
@Bean(name = "jobDetail")
public MethodInvokingJobDetailFactoryBean detailFactoryBean(ScheduleTask task) {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
/*
* 是否并发执行
* 例如每5s执行一次任务,但是当前任务还没有执行完,就已经过了5s了,
* 如果此处为true,则下一个任务会执行,如果此处为false,则下一个任务会等待上一个任务执行完后,再开始执行
*/
jobDetail.setConcurrent(false);
// 设置任务的名字
jobDetail.setName("sayHello");
// 设置任务的分组,这些属性都可以存储在数据库中,在多任务的时候使用
jobDetail.setGroup("sync");
/*
* 为需要执行的实体类对应的对象
*/
jobDetail.setTargetObject(task);

/*
* sayHello为需要执行的方法
* 通过这几个配置,告诉JobDetailFactoryBean我们需要执行定时执行ScheduleTask类中的sayHello方法
*/
jobDetail.setTargetMethod("sayHello");
return jobDetail;
}

@Bean(name = "jobTrigger")
public CronTriggerFactoryBean cronJobTrigger(MethodInvokingJobDetailFactoryBean jobDetail) {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(jobDetail.getObject());
// 获取cron表达式
JobCron jobCron = jobCronService.findByCurrent("1");
// 初始时的cron表达式
trigger.setCronExpression(jobCron.getCron());
// trigger的名称(任意)
trigger.setName("sayHello");
return trigger;
}

@Bean(name = "scheduler")
public SchedulerFactoryBean schedulerFactory(Trigger cronJobTrigger) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
bean.setOverwriteExistingJobs(true);
// 延时启动,应用启动1秒后
bean.setStartupDelay(1);
// 注册触发器
bean.setTriggers(cronJobTrigger);
return bean;
}

}

更新任务执行Cron表达式

从数据库获取最新的Cron表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.ensat.services;

import com.ensat.entities.mysql.JobCron;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Slf4j
@Configuration
@EnableScheduling
@Component
public class ScheduleRefreshDatabase {
@Autowired
private JobCronService jobCronService;

@Resource(name = "jobDetail")
private JobDetail jobDetail;

@Resource(name = "jobTrigger")
private CronTrigger cronTrigger;

@Resource(name = "scheduler")
private Scheduler scheduler;

/**
* 每隔5s查询数据库,并根据查询结果决定是否重新设置定时任务
*/
@Scheduled(fixedRate = 5000)
public void scheduleUpdateCronTrigger() throws SchedulerException {
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(cronTrigger.getKey());
// 当前Trigger使用的cron表达式
String currentCron = trigger.getCronExpression();
// 从数据库查询当前启用的cron表达式
JobCron cron = jobCronService.findByCurrent("1");
// 从数据库查询出来的
String searchCron = cron.getCron();
if (currentCron.equals(searchCron)) {
// 如果当前使用的cron表达式和从数据库中查询出来的cron表达式一致,则不刷新任务
} else {
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);
// 按新的cronExpression表达式重新构建trigger
trigger = (CronTrigger) scheduler.getTrigger(cronTrigger.getKey());
trigger = trigger.getTriggerBuilder().withIdentity(cronTrigger.getKey())
.withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(cronTrigger.getKey(), trigger);
log.info("任务执行时间间隔被修改:[{}]", cron.getDescription());
}
}
}

效果演示

初始化

访问:http:localhost:28080

从图中可以看到目前执行情况为:每秒执行一次,我们看一下控制台输出。

修改任务执行间隔

修改执行时间为:5秒执行一次。查看执行效果

可以看到新的Cron表达式已经生效了。大功告成!

代码获取

Github:

spring-boot-quartz

参考

文章作者: imxushuai
文章链接: https://www.imxushuai.com/2019/06/01/13.Spring-Boot-Quartz-实现动态配置定时任务/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 imxushuai
支付宝打赏
微信打赏