- 源码基于Spring5.0.5版本
- 在SpringBoot项目中只需要添加
@EnableScheduling
即可开启定时任务,在方法添加@Scheduled
注解即可实现固定周期某些方法.- 在
@EnableScheduling
注解中引入了SchedulingConfiguration
配置,而该配置注册了一个名为ScheduledAnnotationBeanPostProcessor
的bean- 看到
BeanPostProcessor
就大致知道该类用于Bean实例化或初始化前后的处理类,以下是该类实现的接口
//核心实现接口MergedBeanDefinitionPostProcessor
public class ScheduledAnnotationBeanPostProcessor
implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
}
该类主要处理业务的方法是
postProcessAfterInitialization
,finishRegistration
,processScheduled
postProcessAfterInitialization
public Object postProcessAfterInitialization(final Object bean, String beanName) {
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
//查找类中包含Scheduled或Schedules注解的方法
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> 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
//将包含@Scheduled或@Schedules注解的方法按类型包装成Task并交由ScheduledTaskRegistrar处理
annotatedMethods.forEach((method, scheduledMethods) ->
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
processScheduled
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
- 上述方法,通过反射将Bean中包含指定注解的方法找出来,并将调用过程包装为Task的子类交由
ScheduledTaskRegistrar
处理 -
Schedules
该注解是个复合注解,一个方法上需要由多种定时调用可以使用该注解,当然如果jdk版本为1.8可以不使用该注解,直接在一个方法上添加多个Scheduled
注解即可 - 代码摘抄了cron表达式包装成
CronTask
的过程 -
Trigger
接口主要是用来计算定时任务下次要执行的时间 -
CronTrigger
类做了以下事- 将cron表达式交由
CronSequenceGenerator
解析 - 下次定时任务要执行的时间也交由
CronSequenceGenerator#next
来计算获取
- 将cron表达式交由
- 真正执行方法调用逻辑是由
TaskScheduler
接口来处理,默认实现ConcurrentTaskScheduler
内部包含一个ScheduledThreadPoolExecutor
线程池(核心线程1个).通过换算出来的下次执行时间及封装好的ScheduledMethodRunnable
即可完成完整的定时调用
题外话
-
@Scheduled#fixedDelay
固定时间执行某个方法(方法完成到下次开始执行时间固定),具体执行在ScheduledTaskRegistrar#scheduleFixedDelayTask
方法 -
@Scheduled#fixedRate
固定周期执行某个方法(方法执行开始时间到下次方法执行开始时间固定),具体执行方法在ScheduledTaskRegistrar#scheduleFixedRateTask
方法 - 如果不想用注解形式实现定时任务可以通过实现
SchedulingConfigurer
接口对ScheduledTaskRegistrar#addTriggerTask
,ScheduledTaskRegistrar#addCronTask
,ScheduledTaskRegistrar#addFixedRateTask
,ScheduledTaskRegistrar#addFixedDelayTask
.同时该接口支持手动创建线程池等信息 -
CronSequenceGenerator
类主要用于解析cron表达式,根据给定的时间推算出下次的时间,内部采用BitSet占位方式将所有要执行的时间点标示出来.#next
方法用来计算下一个时间点的(本人看的有点蒙圈)