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
,这个方法会加载和初始化一下成员变量。
ScheduledAnnotationBeanPostProcessor
该类是通过查找ScheduledTaskRegistrar
的引用找到的一个类,通过名称我们知道,该类就是处理注解Scheduled
的具体实现。通过源码分析,其中有个方法finishRegistration
,这个方法最后一行就调用了接口InitializingBean
的afterPropertiesSet
方法。
动态添加/删除任务实现
通过以上对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
- Spring 源码我本身研究的也不多,有什么不对的地方欢迎大家提出,感谢大家的支持。 ↩︎