通过SchedulingConfigurer 接口完成动态定时任务
一.背景
在Spring中,除了使用@Scheduled注解外,还可以通过实现SchedulingConfigurer接口来创建定时任务。它们之间的主要区别在于灵活性和动态性。@Scheduled注解适用于固定周期的任务,一旦任务的执行时间设定,就无法在运行时动态更改,因此如果需要调整任务执行周期,通常需要停止服务、修改配置,然后重启服务。相比之下,通过实现SchedulingConfigurer接口来配置定时任务可以实现更大的灵活性,可以在运行时动态调整任务的执行时间或周期,而无需停止和重启服务。这种方式特别适合需要根据外部条件或运行时数据动态调整任务调度的场景。
二.实现
2.1.创建数据表
我们创建了一个名为scheduled的数据库表,用于存储定时任务的配置信息,并向该表中插入了一条具体的定时任务配置记录。
CREATE TABLE `scheduled` (`id` INT,`task_key` VARCHAR ( 127 ) COMMENT '任务key值',`name` VARCHAR ( 127 ) COMMENT '任务名称',`cron` VARCHAR ( 63 ) COMMENT '任务表达式',`status` INT DEFAULT 0 COMMENT '状态(1.禁用; 0.启用)',
PRIMARY KEY ( `id` )
) COMMENT = '定时任务配置表';INSERT INTO `scheduled` (`id`, `task_key`, `name`, `cron`, `status`) VALUES (1, 'myTask', '我的定时任务', '0 0/5 * * * ?', 0);
2.2.创建实体类
这段代码使用了JPA(Java Persistence API)注解,定义了一个与数据库表 scheduled 对应的Java实体类 Scheduled。它包含了与定时任务相关的基本属性字段,并通过注解将这些字段映射到数据库表的对应列上。同时,使用Lombok的 @Data 注解简化了实体类的getter、setter等方法的编写。
package com.temperature.humidity.system.entity;import lombok.Data;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;@Table(name = "scheduled")
@Data
@Entity
public class Scheduled {@Id@Column(name = "id")private Integer id;@Column(name = "task_key")private String taskKey;@Column(name = "name")private String name;@Column(name = "cron")private String cron;@Column(name = "status")private Integer status;}
2.3.Dao层接口
通过 taskKey 和 status 属性来查找 Scheduled 实体对象。
package com.temperature.humidity.system.repository.jpa;import com.temperature.humidity.system.entity.Scheduled;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;@Repository
public interface ScheduledRepository extends JpaRepository<Scheduled, Integer> {//通过taskKey和status查找ScheduledScheduled findFirstByTaskKeyAndStatus(String taskKey, Integer status);
}
2.4.定时任务父类
BaseScheduleTask 抽象类提供了一个通用的定时任务结构,使得子类只需实现具体的任务逻辑和 cron 表达式的获取逻辑即可。它利用了 Spring 的定时任务配置支持,通过依赖注入和抽象方法的方式,实现了定时任务的灵活配置和执行。
package com.temperature.humidity.system.schedule;import com.temperature.humidity.system.entity.Scheduled;
import com.temperature.humidity.system.repository.jpa.ScheduledRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;/*** 定时任务父类*/
@Slf4j
public abstract class BaseScheduleTask implements SchedulingConfigurer {private static final String LOG_HEADER = "[定时任务父类]";@Autowiredprivate ScheduledRepository scheduledRepository;@Autowiredprivate TaskScheduler threadPoolTaskScheduler;@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);taskRegistrar.addTriggerTask(//1.添加任务内容(Runnable)this::execTask,//2.设置执行周期(Trigger)triggerContext -> new CronTrigger(this.childGetCron()).nextExecutionTime(triggerContext));}public void execTask() {this.childExec();}/*** 子类实际的工作内容*/public abstract void childExec();/*** 子类获得执行的cron表达式** @return*/public abstract String childGetCron();public abstract String getName();/*** 获取任务执行的Cron表达式*/protected String getTimeCron(String paraSortCode, String name, String defaultValue) {String cron = defaultValue;Scheduled scheduled = scheduledRepository.findFirstByTaskKeyAndStatus(paraSortCode, 0);if (scheduled != null) {cron = scheduled.getCron();}log.info("{},当前任务【{}-{}】,获得的cron表达式:{}", LOG_HEADER, paraSortCode, name, cron);return cron;}
}
2.5.定时任务子类
这里我们举了一个例子,通过继承BaseScheduleTask 并重写其的一些方法来达到动态配置定时任务的目的。
- childExec:实际的工作内容
- childGetCron:获得执行的cron表达式。
- getName:为了控制台打印出一个方便记忆的定时任务名字。
package com.temperature.humidity.system.schedule.task;import com.temperature.humidity.system.schedule.BaseScheduleTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import java.util.Date;@Slf4j
@Component
public class MyTask extends BaseScheduleTask {@Overridepublic void childExec() {log.info("我被执行了当前时间为{}", new Date());}@Overridepublic String childGetCron() {return getTimeCron("myTask", getName(), "0 0/15 * * * ?");}@Overridepublic String getName() {return "我的定时任务";}
}
三.效果
效果如下所示,可以看到实际执行的任务按照数据库配置的corn表达式执行,而不是之前设置的默认的执行。