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就會被激活。如果沒有applicationContext,bean會在所有單例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
:在所有的設置完成后,調用registrar
的afterPropertiesSet
方法,這通常用于初始化和驗證registrar的所有必要屬性。
ScheduledTaskRegistrar
進入ScheduledTaskRegistrar
的afterPropertiesSet
方法,調用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小結
ScheduledTaskRegistrar
的scheduleTasks方法
主要目的是配置和安排各種類型的后臺任務,確保它們能夠按照指定的時間規則(如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源碼初識:理解定時任務拋異常終止本次調度,但不會影響下一次執行調度文章到此結束,創作不易,望我佬們三連支持一下