Spring Boot 动态添加定时任务

  • 实现目标
  • 第一种定时任务实现方式-注解
  • ScheduledTaskRegistrar(注册类)
  • ScheduledAnnotationBeanPostProcessor
  • 动态添加/删除任务实现


实现目标

应用程序在运行期间动态添加、删除定时任务。

第一种定时任务实现方式-注解

@Configuration
@EnableScheduling
public class DemoTask {
	/**
	 * TODO: 每5秒执行一次
	 * @author FantasticTears
	 * @date 2020/9/25
	 */
	@Scheduled(cron = "*/5 * * * * *")
	public void cronTask() {
		System.out.println("每5秒执行一次");
	}
}

注解@Configuration告诉Spring启动时加载该类
注解@EnableScheduling启用计划任务
注解@Scheduled代表具体要执行的任务
Spring 具体加载机制和实现原理请自行研究Spring源码

很明显这种实现方式不符合本文的实现目标,因为这种任务在编译的时候间隔时间和实现的功能已经确定了。说这种实现方式目的是为了引出下一个话题ScheduledTaskRegistrar(注解@EnableScheduling源码上有注释)

ScheduledTaskRegistrar(注册类)

该类总共实现了三个接口ScheduledTaskHolder、InitializingBean、DisposableBean 主要接口是:InitializingBean

public interface InitializingBean {

	/**
	 * Invoked by the containing {@code BeanFactory} after it has set all bean properties
	 * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
	 * <p>This method allows the bean instance to perform validation of its overall
	 * configuration and final initialization when all bean properties have been set.
	 * @throws Exception in the event of misconfiguration (such as failure to set an
	 * essential property) or if initialization fails for any other reason
	 */
	void afterPropertiesSet() throws Exception;

}

该接口是Spring Bean 工厂完成Bean的创建工作后调用的一个方法,俗称Bean后置处理器。

这个类里边维护了应用程序所有的定时任务,大家可以去看下这个类的源码,具体的我就不再赘述了。

核心看下这个方法的实现afterPropertiesSet,这个方法会加载和初始化一下成员变量。

spring动态配置定时任务 spring动态添加定时任务_动态添加


spring动态配置定时任务 spring动态添加定时任务_spring boot_02

ScheduledAnnotationBeanPostProcessor

该类是通过查找ScheduledTaskRegistrar的引用找到的一个类,通过名称我们知道,该类就是处理注解Scheduled的具体实现。通过源码分析,其中有个方法finishRegistration,这个方法最后一行就调用了接口InitializingBeanafterPropertiesSet方法。

spring动态配置定时任务 spring动态添加定时任务_动态添加_03

动态添加/删除任务实现

通过以上对Spring源码的分析我们总结出一个信息,那就是我们只要能拿到ScheduledTaskRegistrar对象,通过调用该对象的一些添加方法,最后调用一下afterPropertiesSet我们就能实现动态添加/删除任务的目标了。那么如何那到ScheduledTaskRegistrar对象呢,其实在注解EnableScheduling的注释里边已经有Demo了,实现SchedulingConfigurer接口即可。

@Configuration
public class DynamicTask implements SchedulingConfigurer {
	private static ScheduledTaskRegistrar scheduledTaskRegistrar;

	@Override
	public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
		log.info("定时任务注册器:{}", scheduledTaskRegistrar);
		DynamicTask.scheduledTaskRegistrar = scheduledTaskRegistrar;
	}

	/**
	 * TODO: 添加定时任务
	 * @author FantasticTears
	 * @date 2020/9/24
	 */
	public static void addScheduledTask(StudyTask studyTask, Check check) {
		List<CronTask> cronTaskList = scheduledTaskRegistrar.getCronTaskList();
		cronTaskList.forEach(v -> {
			if(v.getRunnable() instanceof StudyTask) {
				if(check.check((StudyTask) v.getRunnable())) {
					log.info("任务【{}】已在执行计划中!移除该任务并从新添加!", ((StudyTask) v.getRunnable()).getLearnTaskId());
					cronTaskList.remove(v);
				}
			}
		});
		if(studyTask.hasTask()) {
			scheduledTaskRegistrar.addCronTask(studyTask, "*/10 * * * * ?");
			scheduledTaskRegistrar.afterPropertiesSet();
		}
	}
}

特别注意

List<CronTask> cronTaskList = scheduledTaskRegistrar.getCronTaskList();
		cronTaskList.forEach(v -> {
			if(v.getRunnable() instanceof StudyTask) {
				if(check.check((StudyTask) v.getRunnable())) {
					log.info("任务【{}】已在执行计划中!移除该任务并从新添加!", ((StudyTask) v.getRunnable()).getLearnTaskId());
					cronTaskList.remove(v);
				}
			}
		});

这个代码在运行时会抛异常,写在这里就是给大家一个警示,删除任务不能通过这种方式实现,具体原因大家看下getCronTaskList这个方法的实现就知道了。
那么如何实现删除已经添加过的任务呢。答案是不能删除,只能取消执行

scheduledTaskRegistrar.getScheduledTasks().forEach(v -> {
			if(v.getTask() instanceof CronTask) {
				Task task = v.getTask();
				if(task.getRunnable() instanceof StudyTask) {
					if(check.check((StudyTask) task.getRunnable())) {
						log.info("任务【{}】已在执行计划中!移除该任务并从新添加!", ((StudyTask) task.getRunnable()).getLearnTaskId());
						v.cancel();
					}
				}
			}
		});

至此Spring Boot 定时任务动态添加/删除目标已完成!

结语。1


  1. Spring 源码我本身研究的也不多,有什么不对的地方欢迎大家提出,感谢大家的支持。 ↩︎