Springboot动态增删定时任务
前言
需求 : 根据配置动态生成定时任务去推送数据
大概的流程:
1.mysql中有个配置表,配置表中有过期时间字段和最后更新时间字段
2.定时读取该配置表,如果有新增的配置,需要生成定时任务,如果该配置已经过期了需要取消该定时任务
3.如果该配置进行了更新则需要更新该定时任务
一.最终实现
实现SchedulingConfigurer,拿到ScheduledTaskRegistrar对象(该对象主要负责SpringBoot定时任务的操作)
@Configuration
@EnableScheduling
public class PushScheduleConfigurer implements SchedulingConfigurer {
private static ScheduledTaskRegistrar scheduledTaskRegistrar;
static String cronExpress = "0/3 * * * * ?"; // 默认推送的定时任务每3秒执行一次
public static Set<String> scheduledChannel = new HashSet<>();// 记录哪些配置已经生成定时任务了,避免重复生成定时任务
static Logger log = LoggerFactory.getLogger(PushScheduleConfigurer.class);
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
PushScheduleConfigurer.scheduledTaskRegistrar = scheduledTaskRegistrar;
}
// 定时任务的线程池大小配置
@Bean()
public Executor setTaskExecutors(){
return Executors.newScheduledThreadPool(50); // springboot定时任务线程池大小。
}
// 新增定时任务 AutoPushTask implements Runnable,每个task中有一个channel唯一值
public static void addScheduledTask(AutoPushTask runnable) {
// 每个定时任务有一个唯一值用来标识该定时任务
if(!scheduledChannel .contains(runnable.getChannel())){
// 避免原有的定时任务再次被添加到任务中重复执行
scheduledTaskRegistrar.setTriggerTasks(new HashMap<>());
scheduledTaskRegistrar.setCronTasks(new HashMap<>());
scheduledTaskRegistrar.setFixedRateTasks(new HashMap<>());
scheduledTaskRegistrar.setFixedDelayTasks(new HashMap<>());
scheduledTaskRegistrar.addCronTask(runnable, cronExpress);
// 当执行了afterPropertiesSet后上面的addCronTask才会生效
scheduledTaskRegistrar.afterPropertiesSet();
// 将其加入到缓存中标识该定时任务已经生成
scheduledChannel.add(runnable.getChannel());
log.info(runnable.getChannel() + " - 定时任务被添加");
}
}
// 删除定时任务
public static void deleteSchedule(String channel) {
if(scheduledChannel.contains(channel)) {
scheduledTaskRegistrar.getScheduledTasks().forEach(v -> {
if (v.getTask().getRunnable() instanceof AutoPushTask) {
AutoPushTask t = (AutoPushTask) v.getTask().getRunnable();
if (t.getChannel().equals(channel)) {
v.cancel();
scheduledChannel.remove(channel);
log.info(channel + " - 定时任务已被移除");
return;
}
}
});
}
}
}
二. 遇到过的问题
注: 我本来就需要一个定时任务来定时请求mysql配置表,根据配置表生成定时任务的
1.定时任务出现了重复的问题,直接使用addCronTask(),afterPropertiesSet()定时任务在重复
2.百度了一下,按照他们的方法先把定时任务cancle掉(如上面deleteSchedule)咋一看是解决了,但是当我把定时更新配置文件的定时任务写上去之后,发现我自己定时更新配置文件的定时任务不停的在重复
3.后来看源码了才发现可以通过以下方式直接将跳过已有的定时任务
三.相关源码分析
afterPropertiesSet源码如下
@Override
public void afterPropertiesSet() {
scheduleTasks();
}
protected void scheduleTasks() {
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
从源码中可以看出来taskScheduler并非重新new了一个,所以不会影响到已经存在的定时任务.但是后面又把已有的triggerTasks,cronTasks,fixedRateTasks,fixedDelayTasks重新再往定时任务池中丢了一次,所以我看的很多SpringBoot动态定时任务的博客都是会出现定时任务重复的现象
注: 值于triggerTask,cronTask,fixedRateTask,fixedDelayTask的区别请自行百度
@Nullable
private List<TriggerTask> triggerTasks;
@Nullable
private List<CronTask> cronTasks;
@Nullable
private List<IntervalTask> fixedRateTasks;
@Nullable
private List<IntervalTask> fixedDelayTasks;
// 其他几个是一样的,只举一个做例子参开下
public List<TriggerTask> getTriggerTaskList() {
return (this.triggerTasks != null? Collections.unmodifiableList(this.triggerTasks) :
Collections.emptyList());
}
但是这几个List从外界获取到之后是unmodifiableList,无法对该List进行修改,最后
public void setCronTasks(Map<Runnable, String> cronTasks) {
this.cronTasks = new ArrayList<>();
cronTasks.forEach(this::addCronTask);
}
调用该方法会首先将cronTasks这个List重置掉,所以就可以避免重复的问题了
总结
中间有参考其他人的博客,但是时间过得有点久,没办法贴参考的博客链接.
也不知道这个有没有其他的bug,但是目前我测试了没啥问题,如有问题欢迎提出来.