原文地址:http://tramp.cincout.cn/2017/08/18/spring-task-2017-08-18-spring-boot-enablescheduling-analysis/
核心原理
@EnableScheduling
要使用Spring
的注解@Scheduled
来快速开启任务调度功能,只需要添加如下配置:
@Configuration
@EnableScheduling
public class ScheduleConfig {
}
@EnableScheduling
注解对应的内容如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
由上可以看到实际上是SchedulingConfiguration.class
类实现了Spring
的任务调度框架级功能。该配置类仅仅是定义了ScheduledAnnotationBeanPostProcessor
的实例。Spring 的调度功能由该实例进行配置。
ScheduledAnnotationBeanPostProcessor
该类实现的接口如下所示:
BeanPostProcessor
BeanPostProcessor
作为框架级接口,为实现该接口的类提供了对由 Spring 框架进行组装的单实例 Bean 进行处理的功能。其接口为:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException;
}
实际上,Spring
正是在postProcessAfterInitialization(Object bean, String beanName)
实现了对拥有@Scheduled
注解的实例 Bean 的处理。将其方法添加到对应的任务调度类别中。
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
Class> targetClass = AopUtils.getTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
new MethodIntrospector.MetadataLookup>() {
@Override
public Set inspect(Method method) {
Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
}
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());
}
}
else {
// Non-empty set of methods
for (Map.Entry> entry : annotatedMethods.entrySet()) {
Method method = entry.getKey();
for (Scheduled scheduled : entry.getValue()) {
processScheduled(scheduled, method, bean);
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
上述方法调用的processScheduled(Scheduled scheduled, Method method, Object bean)
实现了对注解@Scheduled
的内容的解析,并将对应的调度任务类型添加到ScheduledTaskRegistrar
实例中。
初始化 TaskScheduler
在 Spring 容器启动时,分别会在afterSingletonsInstantiated()
和 onApplicationEvent(ContextRefreshedEvent event)
方法中调用ScheduledAnnotationBeanPostProcessor.finishRegistration()
。finishRegistration()
的逻辑如下:
-
- 计划任务执行器
TaskScheduler
是否存储,存在就将其传递给ScheduledTaskRegistrar.setScheduler(Object scheduler)
-
-
-
-
检查系统中是否存在实现了回调接口
SchedulingConfigurer
的实例 Bean,如果存在则将现有的ScheduledTaskRegistrar
实例添加到SchedulingConfigurer
中if (this.beanFactory instanceof ListableBeanFactory) { Map
configurers = ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class); for (SchedulingConfigurer configurer : configurers.values()) { configurer.configureTasks(this.registrar); } } 3.如若存在需要调度的任务,同时
TaskScheduler
不存在,则执行分别按类型TaskScheduler.class
和ScheduledExecutorService.class
进行Bean
的查找工作2this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false)); this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, false));
4.调用
ScheduledTaskRegistrar.afterPropertiesSet()
,在该方法中,如果之前步骤都没有找到对应的TaskScheduler
则直接调用Executors.newSingleThreadScheduledExecutor()
构造 5.最终使用的任务执行器为ConcurrentTaskScheduler
public void afterPropertiesSet() { scheduleTasks(); } /** * Schedule all registered tasks against the underlying {@linkplain * #setTaskScheduler(TaskScheduler) task scheduler}. */ 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
实例的初始化过程来看,默认的 Spring 上下文中并不存在实现了TaskScheduler.class
或ScheduledExecutorService.class
接口的 Bean。因此也不能直接@Autowire
。但是我们可以自己定义实现了这些接口的实例,例如ThreadPoolTaskScheduler
。自定义TaskScheduler
实现 SchedulingConfigurer 接口自定义
SchedulingConfigurer.configureTasks(ScheduledTaskRegistrar taskRegistrar)
为我们提供了一个回调。也就是说添加了@Configuration
注解的配置类上,我们可以实现该接口,对系统中真正处理任务的ScheduledTaskRegistrar
类进行修改。
同时,该回调接口为我们实现Trigger-based
的计划任务提供了方法()。 -
@Configuration @EnableScheduling public class ScheduleCallbackConfig implements SchedulingConfigurer { @Bean(name = ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME) public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); // self define error handler //threadPoolTaskScheduler.setErrorHandler(null); return new ThreadPoolTaskScheduler(); } @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setTaskScheduler(threadPoolTaskScheduler()); taskRegistrar.addTriggerTask(new TriggerTask(new Runnable() { @Override public void run() { System.out.println("task implements"); } }, new CronTrigger(""))); } }
直接注入 Bean
直接注入实现了
TaskScheduler.class
或ScheduledExecutorService.class
接口的 Bean 进行自定义。这样启动时,会在 Spring ApplicationContext 上下文查找对应的 Bean。@Configuration @EnableScheduling public class ScheduleCallbackConfig { @Bean(name = ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME) public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(10); // self define error handler //threadPoolTaskScheduler.setErrorHandler(null); return threadPoolTaskScheduler; } }
其他类
ConcurrentTaskScheduler
该类提供了对
java.util.concurrent.ScheduledExecutorService
的适配,同时它能够自动检测JSR-236 javax.enterprise.concurrent.ManagedScheduledExecutorService
,以用来实现trigger-based
计划,代替java.util.concurrent.ScheduledExecutorService
实现的delay-based
计划任务。DefaultManagedTaskScheduler
JNDI-based,在 Java EE 7 环境, 默认查找 JSR-236的 “java:comp/DefaultManagedScheduledExecutorService”。
ThreadPoolTaskScheduler
实现了 Spring 的 TaskScheduler 接口, 包装了原生的
java.util.concurrent.ScheduledThreadPoolExecutor
。分析
默认的
ConcurrentTaskScheduler
计划执行器采用Executors.newSingleThreadScheduledExecutor()
实现单线程的执行器。因此,对同一个调度任务的执行总是同一个线程。如果任务的执行时间超过该任务的下一次执行时间,则会出现任务丢失,跳过该段时间的任务。
上述问题有以下解决办法:- 采用异步的方式执行调度任务,配置 Spring 的
@EnableAsync
,在任务执行的方法上标注@Async
- 配置任务执行池,采用
ThreadPoolTaskScheduler.setPoolSize(n)
。n
的数量为单个任务执行所需时间 / 任务执行的间隔时间
总结
本文详细介绍了 Spring 基于注解实现的计划任务调度功能。并对其实现原理及源代码进行了解析。同时对如何自定义开发进行了说明。由此可见,要理解 Spring 的框架级功能,一定要熟知 Spring Bean 的生命周期。
- 采用异步的方式执行调度任务,配置 Spring 的
-
-
-
-
- 计划任务执行器