【SpringBoot】十二、@Scheduled定时任务(源码)

tech2025-07-17  3

 @Scheduled是Spring task的基于注解的使用方法。Spring task是spring自主开发的轻量级定时任务框架。但是本身不支持持久化,也没有推出官方的分布式集群模式,只能靠开发者在业务应用中自己手动扩展实现,无法满足可视化,易配置的需求。

至于分布式定时任务调度框架,可以看这篇文章分布式定时任务调度框架


目录

1. 入口类声明启用定时任务

2. 创建定时任务类DumpTask

 3. 原理

(1)自定义任务类有@Component和@Scheduled

(2)加了@EnableScheduling


1. 入口类声明启用定时任务

使用@EnableScheduling

@SpringBootApplication @MapperScan("com.winrh.mapper") @ServletComponentScan("com.winrh.filter") @EnableScheduling public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

2. 创建定时任务类DumpTask

@Component注解DumpTask类,@scheduled注解用来配置到方法上来完成对应的定时任务的配置,如执行时间,间隔时间,延迟时间等等。项目开启后自动执行。

/** * 定时备份 * @author ZRH * @version 1.0.0 * @date 2020/9/4 */ @Slf4j @Component public class DumpTask { @Scheduled(cron = "*/10 * * * * ?") public void cron(){ log.info("每隔10秒执行一次"); } @Scheduled(fixedDelay = 4000) public void fixedDelay(){ log.info("循环调用fixedDelay,延迟为4s"); } }

(1)cron,可自定义任意的定时时间,语法可自行百度

{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}

(2)fixedDelay,循环调用本方法,延迟时间可自定义,单位毫秒

(3)其他属性暂不举例

 3. 原理

问题:为什么我入口类加了个@EnableScheduling,Spring Boot就可以开启任务调度,并且自动执行我的定时任务了?

回答:

(1)自定义任务类有@Component和@Scheduled

@Component会在经历refreshContext->...->invokeBeanFactoryPostProcessors->...->processConfigBeanDefinitions->...->registerBeanDefinition中,被扫描并添加到资源池beanDefinitionMap中,供IOC调用(详细过程见我的第二章)。

(2)加了@EnableScheduling

@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Import({SchedulingConfiguration.class}) @Documented public @interface EnableScheduling { }

通过@Import注解注入ScheduilingConfiguration.class这个配置类。看看ScheduilingConfiguration这个类

@Configuration @Role(2) public class SchedulingConfiguration { public SchedulingConfiguration() { } @Bean( name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"} ) @Role(2) public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() { return new ScheduledAnnotationBeanPostProcessor(); } }

其定义了bean:ScheduledAnnotationBeanPostProcessor这么一个后置处理器(因为实现了DestructionAwareBeanPostProcessor,而DestructionAwareBeanPostProcessor又继承了BeanPostProcessor),因此,它会针对每一个bean的创建,扫描下这个bean有没有@Scheduled的方法。简单说下后置处理器搞事情的地方:


① 通过构造器或工厂方法创建 Bean实例② 为 Bean 的属性设置值和对其他 Bean 的引用③ 将 Bean 实例传递给 Bean 后置处理器BeanPostProcessor的 postProcessBeforeInitialization 方法④ 调用 Bean 的初始化方法⑤ 将 Bean 实例传递给 Bean 后置处理器BeanPostProcessor的 postProcessAfterInitialization方法⑥ Bean可以使用了⑦ 当容器关闭时, 调用 Bean 的销毁方法

那么这个后置处理器干了什么?继续看:

public Object postProcessAfterInitialization(Object bean, String beanName) { if (!(bean instanceof AopInfrastructureBean) && !(bean instanceof TaskScheduler) && !(bean instanceof ScheduledExecutorService)) { Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); // nonAnnotatedClasses 是一个缓存,用于记录处理过程中所发现的不包含任何被@Scheduled注解的方法的类 if (!this.nonAnnotatedClasses.contains(targetClass) && AnnotationUtils.isCandidateClass(targetClass , Arrays.asList(Scheduled.class, Schedules.class))) { // 获取类targetClass上所有使用注解@Scheduled的方法 // 某个方法上可能同时使用多个注解@Scheduled, // 故以下annotatedMethods的每个Entry是一个方法对应一个@cheduled集合 Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods (targetClass, (method) -> {Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations (method, Scheduled.class, Schedules.class); return !scheduledMethods.isEmpty() ? scheduledMethods : null; }); if (annotatedMethods.isEmpty()) { // 如果当前类targetClass不包含任何使用注解@Scheduled的方法,将其添加到this.nonAnnotatedClasses this.nonAnnotatedClasses.add(targetClass); if (this.logger.isTraceEnabled()) { this.logger.trace("No @Scheduled annotations found on bean class: " + targetClass); } } else { // 当前类targetClass上找到了使用注解@Scheduled的方法,记录在annotatedMethods中 annotatedMethods.forEach((method, scheduledMethods) -> { scheduledMethods.forEach((scheduled) -> { this.processScheduled(scheduled, method, bean); }); }); if (this.logger.isTraceEnabled()) { this.logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods); } } } return bean; } else { return bean; } }

 而下面的processScheduled方法,会处理方法上的每个@Scheduled注解,生成一个ScheduledTask并登记到this.scheduledTasks。

protected void processScheduled(Scheduled scheduled, Method method, Object bean) { try { // 将用了@Scheduled注解的方法包装成一个Runnable 对象 Runnable runnable = this.createRunnable(bean, method); // 用于记录当前 @Scheduled 注解是否已经被处理,初始化为false boolean processedSchedule = false; String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)'" + ", or 'fixedRate(String)' attributes is required"; // 用于保存针对当前@Scheduled注解生成的ScheduledTask Set<ScheduledTask> tasks = new LinkedHashSet(4); long initialDelay = scheduled.initialDelay(); String initialDelayString = scheduled.initialDelayString(); if (StringUtils.hasText(initialDelayString)) { Assert.isTrue(initialDelay < 0L, "Specify 'initialDelay' " + "or 'initialDelayString', not both"); if (this.embeddedValueResolver != null) { initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString); } if (StringUtils.hasLength(initialDelayString)) { try { initialDelay = parseDelayAsLong(initialDelayString); } catch (RuntimeException var24) { throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long"); } } } // 检查这是否是一个 cron 表达式类型的注解 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 == -1L , "'initialDelay' not supported for cron triggers"); processedSchedule = true; if (!"-".equals(cron)) { TimeZone timeZone; if (StringUtils.hasText(zone)) { timeZone = StringUtils.parseTimeZoneString(zone); } else { timeZone = TimeZone.getDefault(); } // 包装成为一个 CronTask tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable , new CronTrigger(cron, timeZone)))); } } } if (initialDelay < 0L) { initialDelay = 0L; } // 检查这是否是一个固定延迟类型fixedDelay的注解 long fixedDelay = scheduled.fixedDelay(); if (fixedDelay >= 0L) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable , fixedDelay, initialDelay))); } String fixedDelayString = scheduled.fixedDelayString(); if (StringUtils.hasText(fixedDelayString)) { if (this.embeddedValueResolver != null) { fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString); } if (StringUtils.hasLength(fixedDelayString)) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { fixedDelay = parseDelayAsLong(fixedDelayString); } catch (RuntimeException var23) { throw new IllegalArgumentException("Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long"); } // 包装成为一个 FixedDelayTask tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable , fixedDelay, initialDelay))); } } // 检查这是否是一个固定周期执行类型fixedRate的注解 long fixedRate = scheduled.fixedRate(); if (fixedRate >= 0L) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable , fixedRate, initialDelay))); } String fixedRateString = scheduled.fixedRateString(); if (StringUtils.hasText(fixedRateString)) { if (this.embeddedValueResolver != null) { fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString); } if (StringUtils.hasLength(fixedRateString)) { Assert.isTrue(!processedSchedule, errorMessage); processedSchedule = true; try { fixedRate = parseDelayAsLong(fixedRateString); } catch (RuntimeException var22) { throw new IllegalArgumentException("Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long"); } // 包装成为一个 FixedRateTask tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable , fixedRate, initialDelay))); } } Assert.isTrue(processedSchedule, errorMessage); synchronized(this.scheduledTasks) { Set<ScheduledTask> regTasks = (Set)this.scheduledTasks.computeIfAbsent(bean, (key) -> { return new LinkedHashSet(4); }); regTasks.addAll(tasks); } } catch (IllegalArgumentException var25) { throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + var25.getMessage()); } }

 综上,每个bean中所包含的@Scheduled注解都被发现了,这样的每条信息最终对应生成一个ScheduledTask,该ScheduledTask会被ScheduledTaskRegistrar registrar登记调度。这意味着该ScheduledTask从此刻起在程序运行期间就会按照@Scheduled注解所设定的时间点被执行。

 

最新回复(0)