SpringBoot-SchedulingConfigurer源码初识:理解定时任务抛异常终止本次调度,但不会影响下一次执行调度

SchedulingConfigurer源码初识:理解定时任务抛异常终止本次调度,但不会影响下一次执行调度

    • @EnableScheduling
    • ScheduledAnnotationBeanPostProcessor
      • 进入finishRegistration方法
    • ScheduledTaskRegistrar
      • 处理触发器任务(TriggerTask)
      • 进入ReschedulingRunnable的schedule()方法
      • 处理cron任务(CronTask)
      • 处理固定速率任务(IntervalTask)
      • 处理固定延迟任务(IntervalTask)
      • ScheduledTaskRegistrar小结
    • ReschedulingRunnable
      • 进入ReschedulingRunnable的schedule()方法
    • 最终结论

@EnableScheduling

在这里插入图片描述
在这里插入图片描述

也就是我们直接使用的@Scheduled注解配置cron表达式

在这里插入图片描述

ScheduledAnnotationBeanPostProcessor

这个ScheduledAnnotationBeanPostProcessor不仅可以注册我们用@Scheduled注释的方法,它也会检测到我们自定义的定时任务调度配置ScheduledConfigurer实例
在这里插入图片描述

在这里插入图片描述

当我们开启debug模式,进入ScheduledAnnotationBeanPostProcessor里面,程序会先执行无参的ScheduledAnnotationBeanPostProcessor()方法

在这里插入图片描述

创建了一个ScheduledTaskRegistrar对象,并将其赋值给类的registrar成员变量ScheduledTaskRegistrar用于注册定时任务

设置beanName

在这里插入图片描述

设置beanFactory

在这里插入图片描述

​ 用于将一个BeanFactory对象设置为当前对象的属性BeanFactory是Spring框架中用于管理Bean的工厂类,它可以在运行时自动检测和创建Bean实例。通过将BeanFactory对象设置为当前对象的属性,当前对象就可以访问和管理BeanFactory中的所有Bean实例。在注释中提到,设置BeanFactory是可选的如果不设置,则SchedulingConfigurer类型的Bean将不会被自动检测到,需要显式配置一个schedule

在这里插入图片描述

设置applicationContext(上下文),让bean与Spring应用上下文关联,决定bean何时开始活动。当上下文准备好(即所有bean都创建好)时,bean就会被激活。如果没有applicationContextbean会在所有单例bean实例化后激活(即:如果不设置,初始化将在afterSingletonsInstantiated回调方法中发生,这意味着bean的激活会稍晚一些,但仍然在容器完成单例bean实例化之后)。同时,它也用applicationContext来替代可能缺失的BeanFactory

进入afterSingletonsInstantiated后,再进入onApplicationEvent
在这里插入图片描述
在这里插入图片描述

afterSingletonsInstantiated:这个方法在Spring IoC容器初始化并创建了所有单例bean后被调用。此时,所有bean的实例已经创建,但可能还没有全部配置完成。在这个阶段,缓存中的单例类不再需要,所以被清除。如果当前环境不是在ApplicationContext中(比如简单的BeanFactory),那么会立即执行finishRegistration来完成一些早期的任务注册
onApplicationEvent(ContextRefreshedEvent event):这个方法是在ApplicationContext完全初始化并刷新后被调用,即所有bean都已经被实例化、配置并且依赖注入已完成ContextRefreshedEvent是一个事件,表示容器现在处于可用状态。当接收到这个事件时,如果事件源是当前的ApplicationContext,说明容器已经准备就绪,因此可以安全地执行finishRegistration,以完成注册任务。这样做的好处是允许其他监听器有机会在相同的时间点执行它们自己的初始化逻辑,确保所有必要的服务都已设置完毕。

​ 在 ApplicationContext 中运行 -> 注册任务这么晚…让其他 ContextRefreshedEvent 侦听器有机会同时执行他们的工作(例如 Spring Batch 的作业注册)。

进入finishRegistration方法

	private void finishRegistration() {//当前this.scheduler==null,当前对象的scheduler属性未初始化if (this.scheduler != null) {	//检查Scheduler:首先,函数检查当前对象的scheduler属性是否已初始化,如果非空,则将这个scheduler设置给registrar。this.registrar.setScheduler(this.scheduler);}//获取并排序SchedulingConfigurer:接着,如果beanFactory是ListableBeanFactory类型,函数会获取所有实现了SchedulingConfigurer接口的bean,将它们放入一个列表中,并按照AnnotationAwareOrderComparator进行排序。if (this.beanFactory instanceof ListableBeanFactory) {Map<String, SchedulingConfigurer> beans =((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());AnnotationAwareOrderComparator.sort(configurers);for (SchedulingConfigurer configurer : configurers) {configurer.configureTasks(this.registrar);}}if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");try {// Search for TaskScheduler bean...this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));}catch (NoUniqueBeanDefinitionException ex) {if (logger.isTraceEnabled()) {logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +ex.getMessage());}try {this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));}catch (NoSuchBeanDefinitionException ex2) {if (logger.isInfoEnabled()) {logger.info("More than one TaskScheduler bean exists within the context, and " +"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +ex.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex) {if (logger.isTraceEnabled()) {logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +ex.getMessage());}// Search for ScheduledExecutorService bean next...try {this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));}catch (NoUniqueBeanDefinitionException ex2) {if (logger.isTraceEnabled()) {logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +ex2.getMessage());}try {this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));}catch (NoSuchBeanDefinitionException ex3) {if (logger.isInfoEnabled()) {logger.info("More than one ScheduledExecutorService bean exists within the context, and " +"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +ex2.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex2) {if (logger.isTraceEnabled()) {logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +ex2.getMessage());}// Giving up -> falling back to default scheduler within the registrar...logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");}}}this.registrar.afterPropertiesSet();}

检查Scheduler:首先,函数检查当前对象的scheduler属性是否已初始化,如果非空,则将这个scheduler设置给registrar。
在这里插入图片描述

获取并排序SchedulingConfigurer:接着,如果beanFactory是ListableBeanFactory类型,函数会获取所有实现了SchedulingConfigurer接口的bean,将它们放入一个列表中,并按照AnnotationAwareOrderComparator进行排序。

配置Tasks遍历排序后的SchedulingConfigurer列表对每个配置器调用configureTasks方法,允许它们自定义任务调度

在这里插入图片描述

在这里插入图片描述

设置Scheduler

​ 如果registrar有需要执行的任务,但是还没有设置调度器,函数会尝试从beanFactory中找到一个TaskScheduler或者ScheduledExecutorService的bean。这个查找过程首先尝试通过类型匹配,如果找不到,会尝试通过名称匹配(期望的bean名称为’taskScheduler’)。如果仍然找不到,会输出相关信息并回退到使用registrar的内置默认调度器

TaskScheduler
在这里插入图片描述

ScheduledExecutorService
在这里插入图片描述

在这里插入图片描述

初始化registrar:在所有的设置完成后,调用registrarafterPropertiesSet方法,这通常用于初始化和验证registrar的所有必要属性

ScheduledTaskRegistrar

进入ScheduledTaskRegistrarafterPropertiesSet方法,调用scheduleTasks
在这里插入图片描述

检查任务调度器:首先,函数检查是否有已设置的任务调度器(taskScheduler)。如果没有,它会创建一个新的SingleThreadScheduledExecutor,并将其包装为ConcurrentTaskScheduler实例,存储在taskScheduler变量中。

在这里插入图片描述

处理触发器任务(TriggerTask)

处理触发器任务(TriggerTask):如果存在triggerTasks集合,函数会遍历这个集合中的每个触发器任务,并调用scheduleTriggerTask(task)方法来安排任务安排后的任务会被添加到结果列表中。
在这里插入图片描述

进入scheduleTriggerTask(TriggerTask task)方法

在这里插入图片描述

最后,如果任务是新创建的(newTask为true),返回ScheduledTask对象,否则返回null,表示任务已经存在且无需再次安排。

进入ConcurrentTakScheduler类找到对应trigger任务的schedule(Runnable task, Trigger trigger)方法,为什么会进入ConcurrentTakScheduler的找对应的schedule方法?是因为我们前面设置的任务调度器(taskScheduler)。创建一个新的SingleThreadScheduledExecutor,并将其包装为ConcurrentTaskScheduler实例
在这里插入图片描述

根据Trigger对象(任务执行时间的触发器,决定任务何时被调度执行)来计划执行一个Runnable任务。
在这里插入图片描述

返回我们自定义的配置类中

在这里插入图片描述

进入ReschedulingRunnable的schedule()方法

安排一个任务在未来特定时间执行。

	@Nullablepublic ScheduledFuture<?> schedule() {synchronized (this.triggerContextMonitor) {// 1. 同步访问triggerContextMonitor,保证线程安全// 2. 根据触发器和上下文计算任务的下次执行时间this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);if (this.scheduledExecutionTime == null) {    // 3. 如果没有下次执行时间,返回nullreturn null;}// 4. 计算从当前时间到下次执行时间的初始延迟long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();// 5. 使用executor安排任务在初始延迟后执行this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);return this;	// 6. 返回ScheduledFuture对象,用于跟踪任务状态和取消任务}}

完成安排任务,并设置时间间隔,最后将待执行的任务放入this.scheduledTasks

在这里插入图片描述

处理cron任务(CronTask)

处理cron任务(CronTask):对于cronTasks集合中的每个Cron任务,函数调用scheduleCronTask(task)方法,依据cron表达式来安排任务,并将结果添加到结果列表。

处理固定速率任务(IntervalTask)

处理固定速率任务(IntervalTask):如果fixedRateTasks不为空,函数会遍历这个集合,对每个任务调用scheduleFixedRateTask(task),安排以固定速率执行的任务,并将结果保存。

处理固定延迟任务(IntervalTask)

处理固定延迟任务(IntervalTask):最后,对于fixedDelayTasks中的每个任务,调用scheduleFixedDelayTask(task),安排执行完一次后等待固定延迟时间再执行的任务,并添加到结果列表。

在这里插入图片描述

ScheduledTaskRegistrar小结

ScheduledTaskRegistrarscheduleTasks方法主要目的是配置和安排各种类型的后台任务确保它们能够按照指定的时间规则(如cron表达式、固定速率或固定延迟)在后台正确执行

ReschedulingRunnable

最终安排好了任务,进入ReschedulingRunnable的重写的run方法,这个方法执行完,即表明本次定时任务已经完成,根据执行结果和外部条件动态调整后续执行计划是实现定时任务管理和调度的关键部分

	@Overridepublic void run() {//记录实际执行时间,拿到任务开始执行的确切时间Date actualExecutionTime = new Date(this.triggerContext.getClock().millis());super.run();	//调用父类的run方法。这里假设父类的run方法包含了该任务的核心处理逻辑或进一步的委托调用//记录完成时间:在父类的run方法执行完毕后,再次获取当前时间作为任务的完成时间completionTime。这用于计算任务执行的持续时间。Date completionTime = new Date(this.triggerContext.getClock().millis());synchronized (this.triggerContextMonitor) {	//同步并更新上下文//状态检查,确保了任务有一个预定的执行时间,执行逻辑的前提Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");//更新triggerContext上下文,包括预定执行时间、实际执行时间和完成时间,到这一步本次任务已经执行完了this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);//条件性调度: 判断当前任务的未来执行(通过obtainCurrentFuture()获得)有没有被取消if (!obtainCurrentFuture().isCancelled()) {	//根据执行结果和外部条件动态调整后续执行计划schedule();	//安排下一次任务执行}}}

在这里插入图片描述

再次返回我们自定义配置里,执行下一次的任务调度

在这里插入图片描述

在这里插入图片描述

进入ReschedulingRunnable的schedule()方法

最后会不断循环执行的我们的任务

在这里插入图片描述

最后结果

在这里插入图片描述

最终结论

  • 使用继承SchedulingConfigurer接口配置动态定时任务的方式时,主动或者被动抛异常都会终止本次任务的调度,但是不会影响该任务的下一次执行调度
  • 但是如果我们配置的configureTasks方法里面有多个业务方法,其中一个业务方法抛异常,本次任务的调度会马上结束,其它未执行的业务方法将不被执行,所以我们使用定时任务调度实现多个业务方法的时候,需要避免任一出现问题,否则,这次定时任务白忙活了。或者最好一个定时任务,一个业务方法,专人专事

本次SchedulingConfigurer源码初识:理解定时任务抛异常终止本次调度,但不会影响下一次执行调度文章到此结束,创作不易,望我佬们三连支持一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/23092.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

F5G城市光网,助力“一网通城”筑基数字中国

《淮南子》中说&#xff0c;“临河而羡鱼&#xff0c;不如归家织网”。 这句话在后世比喻为做任何事情都需要提前做好准备&#xff0c;有了合适的工具&#xff0c;牢固的基础&#xff0c;各种难题也会迎刃而解。 如今&#xff0c;数字中国发展建设如火如荼&#xff0c;各项任务…

训练营第二十七天 | 491.递增子序列46.全排列47.全排列 II332.重新安排行程51. N皇后

491.递增子序列 力扣题目链接(opens new window) 给定一个整型数组, 你的任务是找到所有该数组的递增子序列&#xff0c;递增子序列的长度至少是2。 示例: 输入: [4, 6, 7, 7]输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]] 说明: …

Python脚本自动填充数据和生成文档轻松办公

一&#xff0c;自动填充数据生成word文档 代码&#xff1a; from docx import Document# 创建一个新的Word文档对象 doc Document()# 添加标题 doc.add_heading(自动填充数据和生成文档, level1)# 添加段落 doc.add_paragraph(这是一个使用Python脚本自动填充数据并生成文档的…

刷新方盒子最快10万销量纪录 捷途旅行者何以颠覆越野市场?

近年”方盒子“产品迅速崛起&#xff0c;在新一轮的市场角逐中&#xff0c;率先突围的并非传统豪强&#xff0c;而是首次进军越野市场的捷途汽车。作为“燃油车&#xff0c;”捷途旅行者&#xff0c;在面对纯电、混动等产品的强势围剿下&#xff0c;仅用时9个月便成为细分市场销…

基于细节增强卷积和内容引导注意的单图像去雾

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;DEA-Net&#xff1a;基于细节增强卷积和内容引导注意的单图像去雾1、研究背景2、方法提出3、相关知识3.1、DEConv3.3、多重卷积的…

C语言指针与数组名的联系

目录 一、数组名的理解 a.数组名代表数组首元素的地址 b. 两个例外 二、使用指针来访问数组 三、一维数组传参的本质 一、数组名的理解 a.数组名代表数组首元素的地址 我们在使用指针访问数组的内容时&#xff0c;有这样的代码&#xff1a; int arr[10] {1,2,3,4,5,6,7,…

枚举(enum)+联合体(union)

枚举联合 一.枚举类型1.枚举类型的声明2.枚举类型的优点3.枚举类型的使用 二.联合体1.联合体类型的声明2.联合体的特点3.相同成员的结构体和联合体对比4.联合体大小的计算5.联合体的练习&#xff08;判断大小端&#xff09;6.联合体节省空间例题 一.枚举类型 1.枚举类型的声明…

Sentinel1.8.6更改配置同步到nacos(项目是Gateway)

本次修改的源码在&#xff1a;https://gitee.com/stonic-open-source/sentinel-parent 一 下载源码 地址&#xff1a;https://github.com/alibaba/Sentinel/releases/tag/1.8.6 二 导入idea&#xff0c;等待maven下载好各种依赖 三 打开sentile-dashboard这个模块&#xf…

华为手机录屏在哪里?图文详解帮你找!

随着科技的进步&#xff0c;智能手机已成为我们日常生活中不可或缺的工具。其中&#xff0c;华为手机凭借其卓越的性能和用户体验&#xff0c;在全球范围内赢得了广泛的赞誉。在众多功能中&#xff0c;录屏功能尤为实用&#xff0c;无论是制作教程、记录游戏精彩瞬间&#xff0…

压敏电阻器是在规定温度下,当电压超过某一临界值时电导随电压的升高而急速增大的一种电阻器

压敏电阻器是在规定温度下,当电压超过某一临界值时电导随电压的升高而急速增大的一种电阻器。压敏电阻器的伏安特性是非线性的,因此,压敏电阻器亦称为非线性电阻器,非线性来自于压敏电阻器两端的外加电压,其伏安特性如图 9-1所示。从图9-1可以看出,压敏电阻器有对称型和非对称型…

网络运维简介

目录 1.网络运维的定义 2.诞生背景 3.网络运维的重要性 4.优点 5.缺点 6.应用场景 6.1.十个应用场景 6.2.数据中心运维 7.应用实例 8.小结 1.网络运维的定义 网络运维&#xff08;Network Operations&#xff09;是指管理、监控和维护计算机网络以确保其高效、安全和…

Python私教张大鹏 Vue3整合AntDesignVue之文本组件

案例&#xff1a;展示标题 核心代码&#xff1a; <a-typography><a-typography-title>Introduction</a-typography-title> </a-typography>vue3示例&#xff1a; <template><a-typography><a-typography-title>这是一个标题</…

【K8s】专题四(6):Kubernetes 控制器之 Job

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、基本介绍 二、工作原理 三、相关特性 四、资源清单&#xff08;示例&#xff09; 五…

电路分析答疑 1

三要素法求解的时候&#xff0c; 电容先求U&#xff0c;再利用求导求I 电感先求I&#xff0c;再利用求导求U 若I的头上没有点点&#xff0c;那就是求有效值 叠加定理&#xff0c;不要忘记 若电流值或者电压值已经给出来了&#xff0c;那就说明这一定是直流电。 在画画圈的时候…

数据库(25)——多表关系介绍

在项目开发中&#xff0c;进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;各个表之间的结构基本上分为三种&#xff1a;一对多&#xff0c;多对多&#xff0c;一对一。 一对多 例如&#xff0c;一个学校可以有…

Mac修改Mysql8.0密码

转载请标明出处&#xff1a;http://blog.csdn.net/donkor_/article/details/139392605 文章目录 前言修改密码Step1:修改my.conf文件Step2:添加配置skip-grant-tablesStep3:重启mysql服务Step4:进入mysqlStep5:刷新权限Step6:修改密码Step7:再次刷新权限Step8:删除/注释 skip-…

DNS域名

DNS域名 DNS是域名系统的简称 域名和ip地址之间的映射关系 互联网中&#xff0c;ip地址是通信的唯一标识 访问网站&#xff0c;域名&#xff0c;ip地址不好记&#xff0c;域名朗朗上口&#xff0c;好记。 域名解析的目的就是为了实现&#xff0c;访问域名就等于访问ip地址…

【Python】 获取当前日期的Python代码解析与应用

标题&#xff1a;Python中获取当前日期的简单指南 基本原理 在Python中&#xff0c;获取当前日期是一个常见的需求&#xff0c;尤其是在处理日志、数据记录和时间相关的任务时。Python提供了多种方式来获取和处理日期和时间&#xff0c;其中最常用的模块是datetime。datetime…

多客陪玩系统-开源陪玩系统平台源码-支持游戏线上陪玩家政线下预约等多场景应用支持H5+小程序+APP

多客陪玩系统-开源陪玩系统平台源码-支持游戏线上陪玩家政按摩线下预约等多场景应用支持H5小程序APP 软件架构 前端&#xff1a;Uniapp-vue2.0 后端&#xff1a;Thinkphp6 前后端分离 前端支持&#xff1a; H5小程序双端APP&#xff08;安卓苹果&#xff09; 安装教程 【商业…

QT C++ QTableWidget 表格合并 setSpan 简单例子

这里说的合并指的是单元格&#xff0c;不是表头。span的意思是跨度、宽度、范围。 setSpan函数需要设定行、列、行跨几格&#xff0c;列跨几格。 //函数原型如下 void QTableView::setSpan(int row, i nt column, 、 int rowSpanCount,/*行跨过的格数*/ int columnSpanCount…