SpringBoot 动态定时任务
最近在工作中遇到了springBoot 动态执行定时任务的场景,通过数据库动态指定任务的执行频率,禁用或启动定时任务等,通过寻找资料,成功解决了这一场景, 一起分享一下吧
1 数据库设计:
CREATE TABLE `spring_scheduled_cron` (
`cron_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`cron_key` varchar(128) NOT NULL COMMENT '定时任务完整类名(该类必须继承 TimingTask类)',
`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`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务表';
2 编写查询数据库的Mapper
public interface ScheduleMapper {
/**
* 获取有效得定时任务
* */
@Select(" select cron_id,cron_key,cron_expression,task_explain,status
from spring_scheduled_cron ")
public List<Schedule> getAll();
}
3 自定义任务类
public abstract class TimingTask implements Runnable {
private Schedule schedule;
public Schedule getSchedule() {
return schedule;
}
public void setSchedule(Schedule schedule) {
this.schedule = schedule;
}
@Override
public void run() {
this.task();
}
//执行的任务
public abstract void task();
@Override
public String toString() {
return schedule.getCronId()+" "+schedule.getCronKey();
}
}
4 编写动态任务配置类
@Configuration
@EnableScheduling
public class DynamicTask implements SchedulingConfigurer {
private static Logger LOGGER = LoggerFactory.getLogger(DynamicTask.class);
//通过 registrar 可以注册 定时任务
private volatile ScheduledTaskRegistrar registrar;
//存放执行器
private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
//存放当前执行的定时任务
private final ConcurrentHashMap<Integer, CronTask> cronTasks = new ConcurrentHashMap<>();
@Autowired
private ScheduleMapper scheduleMapper;
@Autowired
private ApplicationContext applicationContext;
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
this.registrar = registrar;
this.registrar.addTriggerTask(() -> {
//查询定时任务
List<Schedule> scheduleList = scheduleMapper.getAll();
createTask(scheduleList);
}
, triggerContext -> new PeriodicTrigger(5L, TimeUnit.SECONDS).nextExecutionTime(triggerContext));
}
public void createTask(List<Schedule> scheduleList){
if (!CollectionUtils.isEmpty(scheduleList)) {
LOGGER.info("检测动态定时任务列表...");
List<TimingTask> tts = new ArrayList<>();
scheduleList .forEach(taskConstant -> {
Object bean = null;
try {
Class<?> taskClass = Class.forName(taskConstant.getCronKey());
//创建定时任务 去执行
bean = applicationContext.getBean(taskClass);
} catch (ClassNotFoundException e) {
LOGGER.info("定时任务执行器{} 在Spring内找不到!",taskConstant.getCronKey());
return;
}
//获取bean后转换未 TimingTask 对象
if(bean instanceof TimingTask){
TimingTask timingTask = (TimingTask)bean;
timingTask.setSchedule(taskConstant);
tts.add(timingTask);
}
});
this.refreshTasks(tts.toArray(new TimingTask[tts.size()]));
}
LOGGER.info("定时任务加载完成!");
}
private void refreshTasks(TimingTask... tasks) {
Set<Integer> taskIds = scheduledFutures.keySet();
//查看任务是否已经存在,如果存在了就取消
for (Integer taskId : taskIds) {
if (!exists(taskId,tasks)) {
scheduledFutures.get(taskId).cancel(false);
}
}
for (TimingTask timingTask : tasks) {
//校验 cron 表达式是否合法
String expression = timingTask.getSchedule().getCronExpression();
if (StringUtils.isBlank(expression) || !CronSequenceGenerator.isValidExpression(expression)) {
LOGGER.error("定时任务"+timingTask.getSchedule().getCronKey()+" cron表达式不合法: " + expression);
continue;
}
//如果配置一致,并且处于启用状态 则不需要重新创建定时任务
if (scheduledFutures.containsKey(timingTask.getSchedule().getCronId())
&& cronTasks.get(timingTask.getSchedule().getCronId()).getExpression().equals(expression) && timingTask.getSchedule().getStatus().equals("1")) {
continue;
}
//如果策略执行时间发生了变化(如cron表达式修改,状态修改),则取消当前策略的任务,重新创建
if (scheduledFutures.containsKey(timingTask.getSchedule().getCronId())) {
scheduledFutures.remove(timingTask.getSchedule().getCronId()).cancel(false);
cronTasks.remove(timingTask.getSchedule().getCronId());
}
//如果任务有效,才创建
if(timingTask.getSchedule().getStatus().equals("1")){
//创建定时任务执行
CronTask task = new CronTask(timingTask, expression);
ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
cronTasks.put(timingTask.getSchedule().getCronId(), task);
scheduledFutures.put(timingTask.getSchedule().getCronId(), future);
LOGGER.info("添加定时任务===>{} Cron表达式==>{}",timingTask.getSchedule().getCronKey(),timingTask.getSchedule().getCronExpression());
}
}
}
private boolean exists(Integer taskId,TimingTask... tasks) {
for (TimingTask task : tasks) {
if (task.getSchedule().getCronId().equals(taskId)) {
return true;
}
}
return false;
}
@PreDestroy
public void destroy() {
this.registrar.destroy();
}
}
大功告成,启动测试:
在数据库内插入数据,定时任务能正常运行,并且在不重启服务器的情况下通过改变数据库的 cron表达式或状态能够控制定时任务的启动和停止,修改定时任务执行频率
GitHub地址:Spring-Boot-Schedule
参考了几篇大佬的博客:希望同样对您有用: