@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.nonAnnotatedClassesthis.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 注解是否已经被处理,初始化为falseboolean processedSchedule = false;String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)'" +", or 'fixedRate(String)' attributes is required";// 用于保存针对当前@Scheduled注解生成的ScheduledTaskSet<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();}// 包装成为一个 CronTasktasks.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");}// 包装成为一个 FixedDelayTasktasks.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");}// 包装成为一个 FixedRateTasktasks.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注解所设定的时间点被执行。

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

  1. LLVM每日谈之十二 LLVM的源码分析之Pass相关

    作者:snsn1984 题记:在学习LLVM的过程中,要想学的更加深入,掌握更多的技能,LLVM的源码是必须要读的,但是在这么多的源码中,从哪里下手?很容易让人找不到头脑,本文这里就先拿出几个Pass ...

  2. 基于springboot layui二手书交易平台源码

    二手书式的好处有 1.价格便宜 2.节约资源 3.使用的时候还有别人式的笔记,能更好的使用 4.带动了二手书式的市场,促进了经济的发展(更低层人经济的增长) 我挺希望国家能够把教科书纳入循环使用 首先 ...

  3. 第十四课 k8s源码学习和二次开发原理篇-调度器原理

    第十四课 k8s源码学习和二次开发原理篇-调度器原理 tags: k8s 源码学习 categories: 源码学习 二次开发 文章目录 第十四课 k8s源码学习和二次开发原理篇-调度器原理 第一节 ...

  4. Vue+SpringBoot进销存管理系统源码【源码免费分享】

    淘源码-国内专业的免费源码下载平台 Vue+SpringBoot进销存管理系统源码 源码免费分享,需要学习可私信我. 系统介绍: 这是一款面向中小企业的供销链管理系统,基于J2EE快速开发平台Jeec ...

  5. clickhouse原理解析与开发实战 pdf_Spring全家桶集合:SpringBoot+SpringCloud实战,Spring源码原理...

    一.Spring技术内幕(电子书籍赠送) 深入解析Spring架构与设计原理 本书探讨了Spring框架的设计原理.架构和运行机制.作为在Java领域最为成功的开源软件之一,Spring在Java E ...

  6. java计算机毕业设计校园二手书交易系统源码+系统+数据库+lw文档+mybatis+运行部署

    java计算机毕业设计校园二手书交易系统源码+系统+数据库+lw文档+mybatis+运行部署 java计算机毕业设计校园二手书交易系统源码+系统+数据库+lw文档+mybatis+运行部署 本源码技 ...

  7. 基于微信小程序的springboot客运汽车票购票系统源码和论文

    在客运公司工作 7 年之余,对客运管理的难度深有感触.特别是在春运期 间购票难依旧是长途汽车订票的一大难题.长途汽车和火车的订票管理虽然有 差异,但大体上是相同的.长途汽车在售票的过程中需要对旅客的起 ...

  8. 基于springboot的超市管理系统(源码+数据库)003

    代码地址 https://gitee.com/ynwynwy/springboot-thymeleaf-supermarket-system-master-public 基于springboot的超市 ...

  9. Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树

    Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 文章目录 Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 0x00 摘要 0x01 背景概念 1.1 词向量 ...

  10. java毕业设计校园二手书交易平台源码+lw文档+mybatis+系统+mysql数据库+调试

    java毕业设计校园二手书交易平台源码+lw文档+mybatis+系统+mysql数据库+调试 java毕业设计校园二手书交易平台源码+lw文档+mybatis+系统+mysql数据库+调试 开发软件 ...

最新文章

  1. 技术人的不惑之路...... | 每日趣闻
  2. ZT:成熟是一种明亮而不刺眼的光辉
  3. 皮一皮:现在想上个节目一定要注明这些...
  4. 退役笔记一#MySQL = lambda sql : sql + #39; Source Code 4 Explain Plan #39;
  5. z390 黑苹果启动盘_不到800元买块1TB固态,折腾一下黑苹果的安装
  6. python编程对电脑要求_什么是集成开发环境和代码编辑器?优秀Python编程环境的要求(4)...
  7. 转:C#调用C++的DLL搜集整理的所有数据类型转换方式
  8. 如何解决Greenplum中无法通过标准命令修复的元数据错误
  9. 【YOLO家族】【论文翻译】YOLO 9000 /YOLO V2
  10. 水系图一般在哪里找得到_雨水排水系统施工及设备要点详解!
  11. 【数学】GPS经纬度坐标转换
  12. 简单BP神经网络分类手写数字识别0-9
  13. CH340G软件识别、usb转串口软件识别、测试
  14. Mac Gradle 5.0安装
  15. html网页组织结构,使用HTML和CSS编码创建组织结构图
  16. php怎么读取word文档
  17. 数据库设计--实体关系图(ERD)
  18. 续流二级管在电路的作用
  19. 圣杯布局详解--浮动
  20. ios 即时通讯 xmpp

热门文章

  1. 传说中的ARM架构与X86架构
  2. SVM——传说中的核技巧
  3. 可信数智云,联通云的个性化标签
  4. 虚拟人,你们着实有点抢镜了(doge)
  5. 【时间序列】python与时间序列基本教程4(超过1.9万字 代码超过900行 包括49个图)...
  6. 运动场球具的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  7. 如何对人脸识别进行法律规制
  8. 题目:用D触发器带同步高置数和异步高复位端的二分频的电路,画出逻辑电路,Verilog描述。
  9. 常识-idea里鼠标左键选择变成矩形块
  10. 书本例子strindex函数