最简洁的方式实现Springboot动态增删定时任务

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,但是目前我测试了没啥问题,如有问题欢迎提出来.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值