1. Spring 定时任务的简单实现
在Spring Boot
中使用定时任务,只需要@EnableScheduling
开启定时任务支持,在需要调度的方法上添加@Scheduled
注解。这样就能够在项目中开启定时调度功能了,支持通过cron、fixedRate、fixedDelay等灵活的控制执行周期和频率。
1.1 缺点
- 周期一旦指定,想要更改必须要重启应用
1.2 需求
- 热更新定时任务的执行周期,基于cron表达式并支持外部存储,如数据库,nacos等
- 最小改造兼容现有的定时任务(仅需添加一个注解)
- 动态增加定时任务
2.Spring 定时任务源码分析
2.1 @EnableScheduling
引入了配置类 SchedulingConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
2.2 SchedulingConfiguration
只配置了一个bean,ScheduledAnnotationBeanPostProcessor
从名字就知道该类实现BeanPostProcessor
接口
@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();
}
}
2.3 ScheduledAnnotationBeanPostProcessor
的postProcessAfterInitialization
实现,可见具体处理@Scheduled
实现定时任务的是processScheduled
方法
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
// 获取bean的方法及@Scheduled映射关系
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: " + targetClass);
}
}
else {
// Non-empty set of methods
annotatedMethods.forEach((method, scheduledMethods) ->
// 处理@Scheduled注解
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
2.4 以下仅贴出ScheduledAnnotationBeanPostProcessor.processScheduled
处理cron
表达式的关键实现,
private final ScheduledTaskRegistrar registrar;
public ScheduledAnnotationBeanPostProcessor() {
this.registrar = new ScheduledTaskRegistrar();
}
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
// 将定时任务方法,转为Runnable
Runnable runnable = createRunnable(bean, method);
boolean processedSchedule = false;
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// Determine initial delay
// 处理 scheduled.initialDelay()的值,略过...
// Check cron expression
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;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
} else {
timeZone = TimeZone.getDefault();
}
// 创建cron触发器CronTrigger对象,并注册CronTask
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
}
// 处理fixedDelay和fixedRate,及ScheduledTask保存用于销毁,略过...
}
// 略过 catch Exception ...
}
以上通过this.registrar.scheduleCronTask
实现cron定时任务注册或初始化
3.动态定时任务的实现
实现思路: 重写ScheduledAnnotationBeanPostProcessor.processScheduled
方法,修改处理cron的部分代码,使用this.registrar.scheduleTriggerTask
注册或初始化定时任务
3.1 相关类图
DisposableBean+destroy() : voidDynamicCronScheduleTaskManager+Map<String, ScheduledTask> dynamicScheduledTaskMap-ScheduledTaskRegistrar registrar+addTriggerTask(String cronName, TriggerTask task) : ScheduledTask+contains(String cronName) : boolean+updateTriggerTask(String cronName) : void+removeTriggerTask(String cronName) : voidEnvironmentAware+setEnvir