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()方法

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

	@Nullable
	public ScheduledFuture<?> schedule() {
		synchronized (this.triggerContextMonitor) {// 1. 同步访问triggerContextMonitor,保证线程安全
              // 2. 根据触发器和上下文计算任务的下次执行时间
			this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
			if (this.scheduledExecutionTime == null) {    // 3. 如果没有下次执行时间,返回null
				return 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方法,这个方法执行完,即表明本次定时任务已经完成,根据执行结果和外部条件动态调整后续执行计划是实现定时任务管理和调度的关键部分

	@Override
	public 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源码初识:理解定时任务抛异常终止本次调度,但不会影响下一次执行调度文章到此结束,创作不易,望我佬们三连支持一下

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

回溯算法之电话号码字母组合

题目&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "2…

【python】多线程(3)queue队列之不同延时时长的参数调用问题

链接1&#xff1a;【python】多线程&#xff08;笔记&#xff09;&#xff08;1&#xff09; 链接2&#xff1a;【python】多线程&#xff08;笔记&#xff09;&#xff08;2&#xff09;Queue队列 0.问题描述 两个线程&#xff0c;但是不同延时时长&#xff0c;导致数据输出…

vue 引用第三方库 Swpier轮播图

本文全程干货&#xff0c;没有废话 1.使用 npm 安装 swiper&#xff0c;使用 save 保存到 packjson 中 npm install --save swiper 2、把 swiper看成是第三方库或者是组件&#xff0c;然后按照&#xff0c;引用&#xff0c;挂载组件&#xff0c;使用组件三步法。 3、在 script…

overleaf 写参考文献引用

目录 1、 新建.bib 文件 2、导入引用 3、在文档中引用参考文献 4、生成参考文献列表 1、 新建.bib 文件 在Overleaf项目中&#xff0c;你可以选择导入现有的 .bib 文件或在项目中创建一个新的 .bib 文件来管理你的参考文献。 导入.bib 文件&#xff1a; 在项目文件树中点击…

1985-2020 年阿拉斯加和育空地区按植物功能类型划分的模型表层覆盖率

ABoVE: Modeled Top Cover by Plant Functional Type over Alaska and Yukon, 1985-2020 1985-2020 年阿拉斯加和育空地区按植物功能类型划分的模型表层覆盖率 简介 文件修订日期&#xff1a;2022-05-31 数据集版本: 1.1 本数据集包含阿拉斯加和育空地区北极和北方地区按…

C语言| 输出菱形*

C语言| 输出*三角形-CSDN博客 输出菱形。 【分析思路】 学会输出*的三角形之后输出菱形就很简单了。我们分析一下&#xff0c;菱形是由两个对称的三角形组成的&#xff0c;也因为是对称的&#xff0c;所以输出的菱形的行数肯定是一个奇数。 1 我们在编程的时候&#xff0c;要…

网络空间安全数学基础·循环群、群的结构

3.1 循环群&#xff08;重要&#xff09; 3.2 剩余类群&#xff08;掌握&#xff09; 3.3 子群的陪集&#xff08;掌握&#xff09; 3.4 正规子群、商群&#xff08;重要&#xff09; 3.1 循环群 定义&#xff1a;如果一个群G里的元素都是某一个元素g的幂&#xff0c;则G称为…

【SpringBoot】四种读取 Spring Boot 项目中 jar 包中的 resources 目录下的文件

本文摘要&#xff1a;四种读取 Spring Boot 项目中 jar 包中的 resources 目录下的文件 &#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。公粽号&#xf…

python dlib 面部特征点检测

运行环境macos m2芯片 Python 3.11.7&#xff0c;python3.9都能通过&#xff0c;windows系统应该也是一样的效果 import dlib import cv2 import matplotlib.pyplot as plt# Load the image image_path path_to_your_image.jpg # Replace with the path to your image image…

React常见的一些坑

文章目录 两个基础知识1. react的更新问题, react更新会重新执行react函数组件方法本身,并且子组件也会一起更新2. useCallback和useMemo滥用useCallback和useMemo要解决什么3. react的state有个经典的闭包,导致拿不到最新数据的问题.常见于useEffect, useMemo, useCallback4. …

LLM——深入探索 ChatGPT在代码解释方面的应用研究

1.概述 OpenAI在自然语言处理&#xff08;NLP&#xff09;的征途上取得了令人瞩目的进展&#xff0c;这一切得益于大型语言模型&#xff08;LLM&#xff09;的诞生与成长。这些先进的模型不仅是技术创新的典范&#xff0c;更是驱动着如GitHub Copilot编程助手和Bing搜索引擎等广…

基于SpringBoot+Vue的公园管理系统的详细设计和实现(源码+lw+部署文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝1W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还…

西瓜播放器xgplayer设置自动播放踩坑

上图是官网&#xff08;西瓜视频播放器官方中文文档&#xff09;的介绍&#xff0c;相信大家都是按照官网配置去做的&#xff0c;但是并没有什么用&#xff0c;插件很好用&#xff0c;但是属性不全&#xff0c;真的很悔恨&#xff0c;找遍 api 都没有找到自动播放的属性&#x…

epoll模型下的简易版code

epoll模型下的简易版code c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h>#define MAX_EVENTS 10 #define NUM_DESCRIPTORS 5 // 模拟多个文件描述符// …

即时通讯系统是什么?

在信息化发展的时代&#xff0c;人们需要更加高效、便捷的通信方式来满足日常沟通和合作的需求。即时通讯系统应运而生&#xff0c;成为人们日常生活和工作中不可或缺的一部分。即时通讯系统通过互联网或其他网络通信技术实现实时信息传递&#xff0c;为用户提供了文字、语音、…

Adobe InDesign 专业桌面排版软件下载安装,Id软件丰富的排版和设计工具!

Adobe InDesign这款革命性的应用程序不仅彻底改变了出版业的生产流程&#xff0c;更引领着设计领域向前迈进。 在Adobe InDesign的众多强大功能中&#xff0c;对OpenType字体的支持堪称其一大亮点。OpenType字体不仅拥有更加丰富的字体样式和字符集&#xff0c;还具备更为灵活…

遥感之特征选择-禁忌搜索算法

各类智能优化算法其主要区别在于算法的运行规则不同&#xff0c;比如常用的遗传算法&#xff0c;其规则就是变异&#xff0c;交叉和选择等&#xff0c;各种不同的变体大多是在框架内的实现细节不同&#xff0c;而本文中的禁忌算法也是如此&#xff0c;其算法框架如下进行介绍。…

【一刷《剑指Offer》】面试题 31:连续子数组的最大和

牛客对应题目链接&#xff1a;连续子数组最大和_牛客题霸_牛客网 (nowcoder.com) 力扣对应题目链接&#xff1a;53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 核心考点 &#xff1a;简单动归问题。 一、《剑指Offer》对应内容 二、分析题目 1、贪心 从前往后迭…

VRTK4教程 一:资源导入、Unity设置、连接头盔

文章目录 VRTK4的分包导入VRTK4的资源包unity设置连接头盔 VRTK4的分包 vrtk4的资源包和旧版不同&#xff0c;采用了分包导入的思想&#xff0c;我们要用什么功能&#xff0c;就导入什么包&#xff0c;可以有效减小程序体积 如下图&#xff0c;已经导入的vrtk包会显示在Packag…

C语言—深入理解指针(5)

1. sizeof 和 strlen 的对比 1.1 sizeof 在学习操作符的时候&#xff0c;我们学习了 sizeof&#xff0c;sizeof 是计算变量所占内存空间大小的&#xff0c;单位是字节&#xff0c;如果操作数是类型的话&#xff0c;计算的是使用类型创建的变量所占内存空间的大小。 sizeof 只…