【SpringBoot】十二、@Scheduled定时任务(源码)
@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定时任务(源码)相关推荐
- LLVM每日谈之十二 LLVM的源码分析之Pass相关
作者:snsn1984 题记:在学习LLVM的过程中,要想学的更加深入,掌握更多的技能,LLVM的源码是必须要读的,但是在这么多的源码中,从哪里下手?很容易让人找不到头脑,本文这里就先拿出几个Pass ...
- 基于springboot layui二手书交易平台源码
二手书式的好处有 1.价格便宜 2.节约资源 3.使用的时候还有别人式的笔记,能更好的使用 4.带动了二手书式的市场,促进了经济的发展(更低层人经济的增长) 我挺希望国家能够把教科书纳入循环使用 首先 ...
- 第十四课 k8s源码学习和二次开发原理篇-调度器原理
第十四课 k8s源码学习和二次开发原理篇-调度器原理 tags: k8s 源码学习 categories: 源码学习 二次开发 文章目录 第十四课 k8s源码学习和二次开发原理篇-调度器原理 第一节 ...
- Vue+SpringBoot进销存管理系统源码【源码免费分享】
淘源码-国内专业的免费源码下载平台 Vue+SpringBoot进销存管理系统源码 源码免费分享,需要学习可私信我. 系统介绍: 这是一款面向中小企业的供销链管理系统,基于J2EE快速开发平台Jeec ...
- clickhouse原理解析与开发实战 pdf_Spring全家桶集合:SpringBoot+SpringCloud实战,Spring源码原理...
一.Spring技术内幕(电子书籍赠送) 深入解析Spring架构与设计原理 本书探讨了Spring框架的设计原理.架构和运行机制.作为在Java领域最为成功的开源软件之一,Spring在Java E ...
- java计算机毕业设计校园二手书交易系统源码+系统+数据库+lw文档+mybatis+运行部署
java计算机毕业设计校园二手书交易系统源码+系统+数据库+lw文档+mybatis+运行部署 java计算机毕业设计校园二手书交易系统源码+系统+数据库+lw文档+mybatis+运行部署 本源码技 ...
- 基于微信小程序的springboot客运汽车票购票系统源码和论文
在客运公司工作 7 年之余,对客运管理的难度深有感触.特别是在春运期 间购票难依旧是长途汽车订票的一大难题.长途汽车和火车的订票管理虽然有 差异,但大体上是相同的.长途汽车在售票的过程中需要对旅客的起 ...
- 基于springboot的超市管理系统(源码+数据库)003
代码地址 https://gitee.com/ynwynwy/springboot-thymeleaf-supermarket-system-master-public 基于springboot的超市 ...
- Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树
Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 文章目录 Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 0x00 摘要 0x01 背景概念 1.1 词向量 ...
- java毕业设计校园二手书交易平台源码+lw文档+mybatis+系统+mysql数据库+调试
java毕业设计校园二手书交易平台源码+lw文档+mybatis+系统+mysql数据库+调试 java毕业设计校园二手书交易平台源码+lw文档+mybatis+系统+mysql数据库+调试 开发软件 ...
最新文章
- 技术人的不惑之路...... | 每日趣闻
- ZT:成熟是一种明亮而不刺眼的光辉
- 皮一皮:现在想上个节目一定要注明这些...
- 退役笔记一#MySQL = lambda sql : sql + #39; Source Code 4 Explain Plan #39;
- z390 黑苹果启动盘_不到800元买块1TB固态,折腾一下黑苹果的安装
- python编程对电脑要求_什么是集成开发环境和代码编辑器?优秀Python编程环境的要求(4)...
- 转:C#调用C++的DLL搜集整理的所有数据类型转换方式
- 如何解决Greenplum中无法通过标准命令修复的元数据错误
- 【YOLO家族】【论文翻译】YOLO 9000 /YOLO V2
- 水系图一般在哪里找得到_雨水排水系统施工及设备要点详解!
- 【数学】GPS经纬度坐标转换
- 简单BP神经网络分类手写数字识别0-9
- CH340G软件识别、usb转串口软件识别、测试
- Mac Gradle 5.0安装
- html网页组织结构,使用HTML和CSS编码创建组织结构图
- php怎么读取word文档
- 数据库设计--实体关系图(ERD)
- 续流二级管在电路的作用
- 圣杯布局详解--浮动
- ios 即时通讯 xmpp