1. @Scheduled
先看一下@Scheduled源码
package org.springframework.scheduling.annotation;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String cron() default "";
String zone() default "";
long fixedDelay() default -1;
String fixedDelayString() default "";
long fixedRate() default -1;
String fixedRateString() default "";
long initialDelay() default -1;
String initialDelayString() default "";
}
支持cron表达式(”cron” expression)、固定频率(fixedRate)、固定延时(fixedDelay) 3种调度方式。
initial-delay : 表示第一次运行前需要延迟的时间,单位是毫秒
fixed-delay : 表示从上一个任务完成到下一个任务开始的间隔, 单位是毫秒。
fixed-rate : 表示从上一个任务开始到下一个任务开始的间隔, 单位是毫秒。(如果上一个任务执行超时,则可能是上一个任务执行完成后立即启动下一个任务)
cron : cron 表达式。(定时执行,如果上一次任务执行超时而导致某个定时间隔不能执行,则会顺延下一个定时间隔时间。下一个任务和上一个任务的间隔时间不固定)
区别见图

2. ScheduledAnnotationBeanPostProcessor
ScheduledAnnotationBeanPostProcessor是@scheduled注解处理类,实现了BeanPostProcessor接口(postProcessAfterInitialization方法实现注解扫描和类实例创建)、ApplicationContextAware接口(setApplicationContext方法设置当前ApplicationContext)、org.springframework.context. ApplicationListener(观察者模式,onApplicationEvent方法会被回调),DisposableBean接口(destroy方法中进行资源销毁操作)。
ScheduledAnnotationBeanPostProcessor中 postProcessAfterInitialization()扫描所有@scheduled注解,区分cronTasks、fixedDelayTasks、fixedRateTasks。
package org.springframework.scheduling.annotation;
public class ScheduledAnnotationBeanPostProcessor
implements MergedBeanDefinitionPostProcessor, Destruc tionAwareBeanPostProcessor,
Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
}
3. 总体步骤
-
通过
@EnableScheduling注解,创建ScheduledAnnotationBeanPostProcessor类实例;@EnableScheduling注解定义,如下:package org.springframework.scheduling.annotation; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulingConfiguration.class) @Documented public @interface EnableScheduling { }Spring Boot项目中
@EnableScheduling的魔法就在于@Import(SchedulingConfiguration.class),看一下SchedulingConfiguration源码:创建了ScheduledAnnotationBeanPostProcessor实例@Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class SchedulingConfiguration { @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() { return new ScheduledAnnotationBeanPostProcessor(); } } -
ScheduledAnnotationBeanPostProcessor实现BeanPostProcessor接口(postProcessAfterInitialization方法实现@Scheduled注解扫描和类实例创建);postProcessAfterInitialization()调用processScheduled(Scheduled scheduled, Method method, Object bean)实现了对注解@Scheduled的内容的解析,并将对应的调度任务类型添加到ScheduledTaskRegistrar实例中;最后再集中将这些task放入一个regTasks中。【这个regtask在哪里被消费了?】... tasks.add(this.registrar.scheduleFixedRateTask( new FixedRateTask(runnable, fixedRate, initialDelay))); ... // Finally register the scheduled tasks synchronized (this.scheduledTasks) { Set<ScheduledTask> regTasks = this.scheduledTasks .computeIfAbsent(bean, key -> new LinkedHashSet<>(4)); regTasks.addAll(tasks); } -
spring 启动时,
AbstractApplicationContext中的finishRefresh方法触发所有监视者方法回调:ScheduledAnnotationBeanPostProcessor类所实现的ApplicationListener类中的onApplicationEvent()方法。onApplicationEvent()调用finishRegistration()方法完成TaskScheduler的初始化(最终调用的是ConcurrentTaskScheduler); -
默认的
ConcurrentTaskScheduler计划执行器采用Executors.newSingleThreadScheduledExecutor()实现单线程的执行器。因此,对同一个调度任务的执行总是同一个线程。源码如下public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); }
-
如果任务的执行时间超过该任务的下一次执行时间,则会出现任务丢失,跳过该段时间的任务。上述问题有以下解决办法:
- 采用异步的方式执行调度任务,配置 Spring 的
@EnableAsync,在任务执行的方法上标注@Async - 配置任务执行池,采用
ThreadPoolTaskScheduler.setPoolSize(n)。n的数量为单个任务执行所需时间 / 任务执行的间隔时间
- 采用异步的方式执行调度任务,配置 Spring 的
问题:
创建了ConcurrentTaskScheduler 来执行tasks。但是如何将前面的regTasks和这里的executor联系起来的呢?
