@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. 跟我一起写 Makefile(九)
  2. 更改android模拟器sdcard文件夹的权限
  3. Ubuntu 16.04-17.04开机自动root用户登录(测试可用)
  4. day7 java的封装
  5. 关于集簇因子和柱状图的理解
  6. C#.Net工作笔记014---C#中internal的意思
  7. 命名时取代基优先顺序_有机化学专题讲解——有机化合物的命名
  8. Solr Wiki文档
  9. 能力不错的大厂高 P,为什么过不了小厂的试用期?
  10. 二次bootloader关于boot28.asm应用的注意事项,28035的
  11. 教你如何快速识别好固态硬盘?
  12. FME进阶视频教程: FME使用技巧之高级扇出,讲解在FME中输出数据的高级方式,满足数据个性化分类输出的需求
  13. html5 连接wifi,aWiFi是什么?
  14. Excel中时间相减的实例教程
  15. 一人批量管理多个微信群,有什么好用的社群管理工具?
  16. Day 10 你喜欢合租还是独居
  17. JDBC的URL详解
  18. 总结Python语言程序设计课程-推荐的Python第三方库
  19. imx6ull gpio 中断
  20. 深度学习入门——深度学习基础概念思维导图

热门文章

  1. 酒美网宣布完成B轮融资 投资方或为罗斯柴尔德家族
  2. 「需求广场」需求词更新明细(十六)
  3. JavaScript起源及发展
  4. 百奥赛图与TRACON共同宣布YH001(CTLA-4单抗)联合恩沃利单抗(PD-L1)一线治疗软组织肉瘤的临床试验申请获得FDA批准
  5. python数据结构:列表
  6. [翻译]现代java开发指南 第二部分
  7. SCANDY让你的手机变成扫描仪
  8. 路在脚下、梦在身上,技能提升不是为了别人!
  9. 《巴别塔》(Babel)
  10. 单位办公电脑声音和摄像头被关闭,如何打开笔记本麦克风和摄像头。