SpringBoot实现动态的定时任务
一、介绍
SpringBoot项目中,创建定时任务除了使用@Scheduled
注解外,还可以使用 SchedulingConfigurer
。
@Schedule 注解有一个缺点,其定时的时间不能动态的改变,而基于 SchedulingConfigurer 接口的方式可以做到动态的定时任务。
二、依赖
因为这种方式实现动态定时任务需要用到数据库,所以需要mysql驱动、jpa依赖,以及lombok和测试类依赖。
<!--继承SpringBoot父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- SpringBoot测试类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
三、定时任务配置类ScheduledConfig
我们不使用@Scheduled
注解的形式,采用实现SchedulingConfigurer
接口的形式来实现动态定时任务。
原理
@FunctionalInterface
public interface SchedulingConfigurer {
void configureTasks(ScheduledTaskRegistrar var1);
}
实现SchedulingConfigurer
接口需要重写configureTasks
方法。
然后使用ScheduledTaskRegistrar
调用addTriggerTask
方法。
使用addTriggerTask方法就是增加一个触发任务。
public void addTriggerTask(Runnable task, Trigger trigger) {
this.addTriggerTask(new TriggerTask(task, trigger));
}
这个方法的参数有两个:
第一个参数task,简单解释就是我们要执行的定时时间,这个事件要实现Runnable接口,就是创建一个线程。
第二个参数trigger,就是触发器,点进Trigger
这个类,
public interface Trigger {
@Nullable
Date nextExecutionTime(TriggerContext var1);
}
nextExecutionTime
就是下一次执行的时间,所以我们可以发现这个Trigger的作用就是控制时间下一次执行的时间。
Trigger类这个就是我们实现动态控制定时任务的关键!
另外CronTrigger类实现了Trigger类,
所以我们在这里查询数据库中cron表达式,赋值到这里。这样我们通过修改数据库中的cron表达式,就可以达到动态的控制定时任务的执行了。
完整的配置类代码
package com.lsh.config;
import com.lsh.entity.SpringScheduledCron;
import com.lsh.respsitory.SpringScheduledCronRepository;
import com.lsh.task.DynamicPrintTask3;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @author :LiuShihao
* @date :Created in 2020/8/24 4:36 下午
* @desc :查询数据库cron表达式 动态切换cron表达式
*
*/
@Slf4j
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Autowired
private SpringScheduledCronRepository cronRepository;
//注入我们的定时任务类
@Autowired
DynamicPrintTask task;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//可以通过改变数据库数据进而实现动态改变执行周期
/**
*
* public void addTriggerTask(Runnable task, Trigger trigger) {
* this.addTriggerTask(new TriggerTask(task, trigger));
* }
* 使用这个方法会增加一个触发任务 :即 !这个定时任务的下一次执行时间!
* 参数:Runnable 一个事件
* 参数:Trigger 一个触发器
* public interface Trigger {
* @Nullable
* Date nextExecutionTime(TriggerContext var1); 下一次执行时间
* }
*/
taskRegistrar.addTriggerTask(((Runnable) task3),
triggerContext -> {
//查询数据库 获得该类的cron表达式
String cronExpression = cronRepository.findSpringScheduledCronByCronKey(task3.getClass().getName()).getCronExpression();
log.info("cronRepository.findByCronExpression():"+cronExpression);
// CronTrigger实现了Trigger
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
}
@Bean
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
}
四、定时任务类
我们的定时任务类必须实现Runnable接口!
package com.lsh.task;
import com.lsh.respsitory.SpringScheduledCronRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author :LiuShihao
* @date :Created in 2020/8/24 4:53 下午
* @desc :
*/
@Component
public class DynamicPrintTask implements Runnable {
public static Boolean isRun = false;
@Autowired
SpringScheduledCronRepository springScheduledCronRepository;
@Override
public void run() {
if (isRun) return;
isRun = true;
//通过类名获得表达式信息 判断状态是否可用 1:正常 2:停用 如果不可用直接返回
//this.getClass().getName() 获得当前类名 com.lsh.task.DynamicPrintTask3
Integer status = springScheduledCronRepository.findSpringScheduledCronByCronKey(this.getClass().getName()).getStatus();
if (2 == status){
return;
}
System.out.println("-----=====定时任务3:"+ LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
isRun = false;
}
}
五、JPA
package com.lsh.respsitory;
import com.lsh.entity.SpringScheduledCron;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
/**
* @author :LiuShihao
* @date :Created in 2020/8/24 4:58 下午
* @desc :
*/
public interface SpringScheduledCronRepository extends JpaRepository<SpringScheduledCron, Integer> {
//根据CronKey查找表达式
SpringScheduledCron findSpringScheduledCronByCronKey(String CronKey);
}
六、实体类
package com.lsh.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author :LiuShihao
* @date :Created in 2020/8/24 5:01 下午
* @desc :
*/
@Data
@Entity(name = "spring_scheduled_cron")
public class SpringScheduledCron {
@Id
@Column(name = "cron_id")
private Integer cronId;
@Column(name = "cron_key", unique = true)
private String cronKey;
@Column(name = "cron_expression")
private String cronExpression;
@Column(name = "task_explain")
private String taskExplain;
private Integer status;
}
sql语句
CREATE TABLE `spring_scheduled_cron` (
`cron_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`cron_key` varchar(128) NOT NULL COMMENT '定时任务完整类名',
`cron_expression` varchar(20) NOT NULL COMMENT 'cron表达式',
`task_explain` varchar(50) NOT NULL DEFAULT '' COMMENT '任务描述',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:正常;2:停用',
PRIMARY KEY (`cron_id`),
UNIQUE KEY `cron_key` (`cron_key`),
UNIQUE KEY `cron_key_unique_idx` (`cron_key`)
)
七、yml配置文件
spring:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/day27
username: root
password: 123456
jpa:
show-sql: true
server:
port: 8089
八、主启动类
@SpringBootApplication
@EnableScheduling
public class TimerTaskApplication {
public static void main(String[] args) {
SpringApplication.run(TimerTaskApplication.class);
}
}
九、启动测试
表数据:
缺点
不过这种方式也有缺点,就是增加了数据库的压力,长时间占着数据库的连接,查询基数大。
我们可以引入缓存来减轻数据库压力,减少内存消耗
缓存依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
另外除了引入缓存,还用另外一种方式来解决这个问题,就是使用MQ延时队列
也可以实现动态的控制定时任务使用MQ延时队列来解决动态定时任务问题。