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.pswp.cn/web/23092.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23092.shtml
英文地址,請注明出處:http://en.pswp.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]] 說明: …

S4 BP 常用tcode

FLBPD1 - 從客戶創建業務伙伴 FLBPC1 - 從供應商處創建業務合作伙伴 FLBPD2 - 將業務伙伴鏈接到客戶 FLBPC2 - 業務合作伙伴到供應商的鏈接 CVI_CUSTOMIZING_CHK - 事務 CVI_CUSTOMIZING_CHK CVI_PRECHK - 事務 CVI_PRECHK CVI_COCKPIT - 事務 CVI_COCKPIT MDS_LINKS - …

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、多重卷積的…

深度學習 - 構建神經網絡

1. 自動求導機制 概念解釋&#xff1a; 自動求導&#xff1a;PyTorch的autograd模塊允許我們自動計算張量的梯度&#xff0c;這在反向傳播算法中尤為重要。反向傳播是神經網絡訓練的核心&#xff0c;用于計算每個參數的梯度并更新參數。 生活中的例子&#xff1a; 想象你是…

Java時間類(十六) -- 將一天的時間進行等步長分割

廢話不多說,直接上工具類: import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List;/*** @ClassName TimeSplitterUtil* @Description …

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…

介紹下CIDR(Classless Inter-Domain Routing)無類別域間路由

最近在搞DELL EMC XtremIO的重新初始化&#xff0c;在Stortage controller和XMS的xinstall配置的時候&#xff0c;需要配置用到CIDR&#xff0c;就是classless inter-domian routing&#xff0c;總結了一下&#xff0c;其實很多對網絡設備的地方都用得到&#xff0c;以前還不知…

華為手機錄屏在哪里?圖文詳解幫你找!

隨著科技的進步&#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;是指管理、監控和維護計算機網絡以確保其高效、安全和…

2024最新華為OD算法題目

在一個機房中,服務器的位置標識在 n*m 的整數矩陣網格中,1表示單元格上有服務器,0 表示沒有。如果兩臺服務器位于同一行或者同一列中緊鄰的位置,則認為它們之間可以組成一個局域網。請你統計機房中最大的局域網包含的服務器個數。 輸入描述 第一行輸入兩個正整數,n和m,…

Python私教張大鵬 Vue3整合AntDesignVue之文本組件

案例&#xff1a;展示標題 核心代碼&#xff1a; <a-typography><a-typography-title>Introduction</a-typography-title> </a-typography>vue3示例&#xff1a; <template><a-typography><a-typography-title>這是一個標題</…

HTTP請求過程

HTTP&#xff08;超文本傳輸協議&#xff09;請求過程是客戶端&#xff08;通常是瀏覽器&#xff09;與服務器之間通信的方式&#xff0c;用于從服務器請求資源&#xff08;如網頁、圖片、視頻等&#xff09;。以下是HTTP請求的基本步驟&#xff1a; 建立TCP連接&#xff1a; 如…

【K8s】專題四(6):Kubernetes 控制器之 Job

以下內容均來自個人筆記并重新梳理&#xff0c;如有錯誤歡迎指正&#xff01;如果對您有幫助&#xff0c;煩請點贊、關注、轉發&#xff01;歡迎掃碼關注個人公眾號&#xff01; 目錄 一、基本介紹 二、工作原理 三、相關特性 四、資源清單&#xff08;示例&#xff09; 五…

C語言經典習題20

一編寫一個函數用于計算高于平均分的人數 編寫一個函數int fun(float s[],int n)&#xff0c;用于計算高于平均分的人數&#xff0c;并作為函數值返回&#xff0c;其中數組s中存放n位學生的成績。再編寫一個主函數&#xff0c;從鍵盤輸入一批分數&#xff08;用-1來結束輸入&a…