Spring @Scheduled學習

一. Jdk中的定時任務

我們平時在 Spring 項目中會使用 @Scheduled 開啟定時任務;

jdk 中其實也提供了定時任務線程池 ScheduledThreadPool,我們可以直接通過 Executors 工具類獲取;

// 創建了核心線程數為 2 的 ScheduledThreadPool 對象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);executorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("正在執行任務1,線程名:" + Thread.currentThread().getName());}
}, 0, 3, TimeUnit.SECONDS);executorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("正在執行任務2,線程名:" + Thread.currentThread().getName());}
}, 0, 3, TimeUnit.SECONDS);

這里會啟用兩個線程去執行定時任務,打印如下;

正在執行任務1,線程名:pool-1-thread-1
正在執行任務2,線程名:pool-1-thread-1
正在執行任務1,線程名:pool-1-thread-1
正在執行任務2,線程名:pool-1-thread-2

二. Spring中的定時任務

我們知道:要開啟 Spring 的定時任務,也就是要使用 @Scheduled 注解的話,需要 @EnableScheduling 啟用定時任務;

下面我們從源碼的角度來看一下 Spring 中的定時任務;

1. Spring中默認的TaskScheduler

Spring 項目中會存在一個默認的 taskScheduler 對象,它是一個 ThreadPoolTaskScheduler;

是在 TaskSchedulingAutoConfiguration 中導入的,可以看到會往 Spring 容器中注入一個 beanName 叫 “taskScheduler” 的 ThreadPoolTaskScheduler 對象;

@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@AutoConfiguration(after = TaskExecutionAutoConfiguration.class)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
public class TaskSchedulingAutoConfiguration {@Bean@ConditionalOnBean(name = "internalScheduledAnnotationProcessor")@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {// 通過 TaskSchedulerBuilder.build() 構建 ThreadPoolTaskScheduler 對象return builder.build();}@Bean@ConditionalOnMissingBeanpublic TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,                                                   ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {// 創建出 TaskSchedulerBuilderTaskSchedulerBuilder builder = new TaskSchedulerBuilder();// 設置 TaskSchedulerBuilder 的核心線程數builder = builder.poolSize(properties.getPool().getSize());Shutdown shutdown = properties.getShutdown();builder = builder.awaitTermination(shutdown.isAwaitTermination());// 設置 TaskSchedulerBuilder 的線程名前綴builder = builder.threadNamePrefix(properties.getThreadNamePrefix());builder = builder.customizers(taskSchedulerCustomizers);// 返回 TaskSchedulerBuilder 對象return builder;}
}

2. @Scheduled

我們需要知道是哪個類來解析 @Scheduled 注解的;

/*** 從注解的信息可以看出解析是在 ScheduledAnnotationBeanPostProcessor* 我們需要重點看 ScheduledAnnotationBeanPostProcessor 類** @see EnableScheduling* @see ScheduledAnnotationBeanPostProcessor* @see Schedules*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {long fixedDelay() default -1;long fixedRate() default -1;String cron() default "";
}

那這個 ScheduledAnnotationBeanPostProcessor 又是從哪注入到 Spring 容器的呢?其實是在 @EnableScheduling 中注入的;

3. @EnableScheduling

我們看一下 @EnableScheduling,它往 spring 容器中注入了一個配置類:SchedulingConfiguration;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)// 往 Spring 容器中注入 SchedulingConfiguration 類
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {}

我們看一下 SchedulingConfiguration 類,它往容器中注入了 ScheduledAnnotationBeanPostProcessor 對象;

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {// 往容器中注入了 ScheduledAnnotationBeanPostProcessor 對象// 并且它的 beanName 為 "internalScheduledAnnotationProcessor"// 正因為導入了 "internalScheduledAnnotationProcessor",// taskScheduler 對象才會被注入到 Spring 容器中@Bean(name = "internalScheduledAnnotationProcessor")public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();}}

至此,前置完成,解析 @Scheduled 注解的任務交給了 ScheduledAnnotationBeanPostProcessor,我們需要重點看 ScheduledAnnotationBeanPostProcessor 做了啥;

三. ScheduledAnnotionBeanPostProcessor

  1. ScheduledAnnotionBeanPostProcessor 實現了 BeanPostProcessor 接口,它的 postProcessAfterInitialization() 會解析 bean 中的 @Scheduled 注解;
  2. ScheduledAnnotionBeanPostProcessor 實現了 ApplicationListener 接口,它的 onApplication(ContextRefreshedEvent event) 會在 spring 刷新完 beanFactory 容器的時候調用,啟用定時任務;

我們也主要從這兩個方法入手;

1. postProcessAfterInitialization()

postProcessAfterInitialization() 如下;

// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
public Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler || bean instanceof ScheduledExecutorService) {return bean;}Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);// 1. 遍歷類中的每一個方法,收集帶有 @Scheduled 注解的方法if (AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {Map<Method, Set<Scheduled>> annotatedMethods;// 構建 annotatedMethodsif (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);} else {// 2. 輪詢 annotatedMethods// 對 @Scheduled 注解的方法執行 processScheduled()annotatedMethods.forEach((method, scheduledAnnotations) ->scheduledAnnotations.forEach(scheduled ->                             						processScheduled(scheduled, method, bean)));}}return bean;
}

processScheduled(scheduled, method, bean) 中,scheduled 為方法上的 @Scheduled 對象;我們看下 processScheduled() 的過程,我們只關注用的多的 cron 表達式;

// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {// 1. 將 bean 和 method 包裝為 runnable 對象Runnable runnable = createRunnable(bean, method);boolean processedSchedule = false;Set<ScheduledTask> tasks = new LinkedHashSet<>(4);// 2. 解析 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);}processedSchedule = true;if (!Scheduled.CRON_DISABLED.equals(cron)) {TimeZone timeZone;if (StringUtils.hasText(zone)) {timeZone = StringUtils.parseTimeZoneString(zone);}else {timeZone = TimeZone.getDefault();}// 3. 往 this.registrar 中添加 CronTask 任務// this.registrar 為 ScheduledTaskRegistrar 類對象tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));}}
}// ------------------------ ScheduledTaskRegistrar -------------------------
public ScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {// 1. 第一次進來的時候創建 scheduledTaskscheduledTask = new ScheduledTask(task);newTask = true;}// 2. 第一次進來的時候創建 this.taskScheduler == null,走 else 邏輯if (this.taskScheduler != null) {scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());} else {// 3. 將 task 放入到當前的 cronTask 中addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}

至此,bean 中的 @Scheduled 注解的定時任務都被包裝為 cronTask 對象放入到 ScheduledTaskRegistrar 中;

2. onApplication(ContextRefreshEvent)

Spring 刷新完 beanFactory 容器的時候會調用該方法,啟用定時任務;

// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
public void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.applicationContext) {// 調用 finishRegistration()finishRegistration();}
}// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
private void finishRegistration() {if (this.scheduler != null) {this.registrar.setScheduler(this.scheduler);}// 1. 先查找 SchedulingConfigurer,如果有的話用 SchedulingConfigurerif (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) {try {// 2. 尋找 TaskScheduler bean,通過 byType 的方式// 往 this.registrar 中設置定時線程池this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));} catch (NoUniqueBeanDefinitionException ex) {// 2.1 TaskScheduler bean 不唯一,通過 byName 的方式,注入 "taskScheduler"// 尋找 TaskScheduler bean,通過 byName 的方式// 一般注入的都是默認的 "taskScheduler"this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));} catch (NoSuchBeanDefinitionException ex) {try {// 3. 尋找 ScheduledExecutorService bean,通過 byType 的方式this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));} catch (NoUniqueBeanDefinitionException ex2) {// 3.1 ScheduledExecutorService bean 不唯一// 尋找 ScheduledExecutorService bean,通過 byName 的方式this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));} catch (NoSuchBeanDefinitionException ex2) {logger.info("沒有 TaskScheduler/ScheduledExecutorService bean")}}}// 4. 上述往 this.registrar 中設置了 taskScheduler 對象// 執行 this.registrar.afterPropertiesSet()this.registrar.afterPropertiesSet();
}

我們看下 this.registrar.afterPropertiesSet() 做了啥;

// ------------------------ ScheduledTaskRegistrar -------------------------
public void afterPropertiesSet() {scheduleTasks();
}// ------------------------ ScheduledTaskRegistrar -------------------------
protected void scheduleTasks() {// 1. 如果 this.taskScheduler == null// 創建單核心線程的 ThreadScheduledExecutor 作為定時線程池// 一般 this.taskScheduler 不會為 nullif (this.taskScheduler == null) {this.localExecutor = Executors.newSingleThreadScheduledExecutor();this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);}if (this.triggerTasks != null) {for (TriggerTask task : this.triggerTasks) {addScheduledTask(scheduleTriggerTask(task));}}// 2. this.cronTasks 不為 null// 依次遍歷 cronTask,執行 scheduleCronTask(cronTask)// 這是我們第二次進入 scheduleCronTask(cronTask),和第一次有點區別if (this.cronTasks != null) {for (CronTask task : this.cronTasks) {addScheduledTask(scheduleCronTask(task));}}if (this.fixedRateTasks != null) {for (IntervalTask task : this.fixedRateTasks) {addScheduledTask(scheduleFixedRateTask(task));}}if (this.fixedDelayTasks != null) {for (IntervalTask task : this.fixedDelayTasks) {addScheduledTask(scheduleFixedDelayTask(task));}}
}// ------------------------ ScheduledTaskRegistrar -------------------------
public ScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {scheduledTask = new ScheduledTask(task);newTask = true;}// 1. 第二次進來,this.taskScheduler 不為 null// 執行 this.taskScheduler.schedule(),正式啟動定時任務if (this.taskScheduler != null) {scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());} else {addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}

至此,SpringScheduled 全部解析完畢;

四. 配置定時線程池

1. 通過配置文件

我們在 TaskSchedulingAutoConfiguration 中知道,其實默認的 ThreadPoolTaskScheduler 都是根據配置項 TaskSchedulingProperties 創建的,默認核心線程 coreThreads = 1;

可以進行如下配置:

spring:task:scheduling:pool:size: 5thread-name-prefix: my-schedule-

2. 自定義ThreadPoolTaskScheduler

我們也可以直接往 Spring 容器中自定義注入 ThreadPoolTaskScheduler 對象,只不過需要注意它的 beanName 必須為 taskScheduler;

@Configuration
public class ThreadPoolConfig {@Beanpublic Executor taskScheduler() {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(3);taskScheduler.setThreadNamePrefix("my-schedule-task-");taskScheduler.initialize();return taskScheduler;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44817.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44817.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44817.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ROS2 + 科大訊飛 初步實現機器人語音控制

環境配置&#xff1a; 電腦端&#xff1a; ubuntu22.04實體機作為上位機 ROS版本&#xff1a;ros2-humble 實體機器人&#xff1a; STM32 思嵐A1激光雷達 科大訊飛語音SDK 訊飛開放平臺-以語音交互為核心的人工智能開放平臺 實現步驟&#xff1a; 1. 下載和處理科大訊飛語音模…

開發指南048-前端模塊版本

平臺前端框架內置了一個文件version.vue <template> <div> <br> 應用名稱: {{name}} <br> 當前版本&#xff1a;{{version}} <br> 服務網關: {{gateway}} </div> </template> <scrip…

qt 創建一個包含兩按鈕,且安裝和自定義控件間沒有間距

在 Qt 中創建一個包含兩個按鈕且按鈕之間沒有間距的自定義控件&#xff0c;你可以使用 QHBoxLayout 或 QVBoxLayout&#xff08;取決于你希望按鈕是水平排列還是垂直排列&#xff09;&#xff0c;并設置布局的間距為 0。以下是一個簡單的示例&#xff0c;展示了如何創建一個水平…

Dataset for Stable Diffusion

1.Dataset for Stable Diffusion 筆記來源&#xff1a; 1.Flickr8k數據集處理 2.處理Flickr8k數據集 3.Github&#xff1a;pytorch-stable-diffusion 4.Flickr 8k Dataset 5.dataset_flickr8k.json 1.1 Dataset 采用Flicker8k數據集&#xff0c;該數據集有兩個文件&#xff…

Node.js_mongodb用戶名和密碼操作

mongodb用戶名和密碼操作 查看用戶密碼創建管理員用戶和密碼mongodb的目標是實現快速簡單部署,所以存在很多安全問題 默認配置下沒有用戶和密碼,無需身份驗證即可登錄,不像mysql那樣需要登錄才能操作數據庫本身安全問題:升級3.0以上版本查看用戶密碼 密碼是加密存儲的,并且…

前端工程化10-webpack靜態的模塊化打包工具之各種loader處理器

9.1、案例編寫 我們創建一個component.js 通過JavaScript創建了一個元素&#xff0c;并且希望給它設置一些樣式&#xff1b; 我們自己寫的css,要把他加入到Webpack的圖結構當中&#xff0c;這樣才能被webpack檢測到進行打包&#xff0c; style.css–>div_cn.js–>main…

速盾:ddos高防ip哪里好用?

隨著互聯網的飛速發展&#xff0c;DDoS攻擊問題逐漸突出。DDoS攻擊是一種通過在網絡上創建大量請求&#xff0c;使目標網絡或服務器過載而無法正常工作的攻擊方式。為了應對DDoS攻擊&#xff0c;提高網絡的安全性和穩定性&#xff0c;使用高防IP成為了一種常見的解決辦法。 DD…

Flower花所比特幣交易及交易費用科普

在加密貨幣交易中&#xff0c;選擇一個可靠的平臺至關重要。Flower花所通過提供比特幣交易服務脫穎而出。本文將介紹在Flower花所進行比特幣交易的基礎知識及其交易費用。 什么是Flower花所&#xff1f; Flower花所是一家加密貨幣交易平臺&#xff0c;為新手和資深交易者提供…

【C++】開源:drogon-web框架配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 這篇文章主要介紹drogon-web框架配置使用。 無專精則不能成&#xff0c;無涉獵則不能通。——梁啟超 歡迎來到我的博客&#xff0c;一起學習&#xff0c;共同進步。 喜歡的朋友可以關注一下&#xff0c;…

Linux系統編程-線程同步詳解

線程同步是指多個線程協調工作&#xff0c;以便在共享資源的訪問和操作過程中保持數據一致性和正確性。在多線程環境中&#xff0c;線程是并發執行的&#xff0c;因此如果多個線程同時訪問和修改共享資源&#xff0c;可能會導致數據不一致、競態條件&#xff08;race condition…

面試題008-Java-SpringBoot

面試題008-Java-SpringBoot 目錄 面試題008-Java-SpringBoot題目自測題目答案1. Spring 和 Spring Boot有什么區別&#xff1f;2. Spring Boot 的主要優點是什么&#xff1f;3. 什么是Spring Boot Starter&#xff1f;4. 介紹一下SpringBootApplication注解&#xff1f;5. Spri…

【密碼學】消息認證

你發送給朋友一條消息&#xff08;內容&#xff1a;明天下午來我家吃飯&#xff09;&#xff0c;這一過程中你不想讓除你朋友以外的人看到消息的內容&#xff0c;這就叫做消息的機密性&#xff0c;用來保護消息機密性的方式被叫做加密機制。 現在站在朋友的視角&#xff0c;某一…

使用PyQt5實現添加工具欄、增加SwitchButton控件

前言&#xff1a;通過在網上找到的“電池電壓監控界面”&#xff0c;學習PyQt5中添加工具欄、增加SwitchButton控件&#xff0c;在滑塊控件右側增加文本顯示、設置界面背景顏色、修改文本控件字體顏色等。 1. 上位機界面效果展示 網絡上原圖如下&#xff1a; 自己使用PyQt5做…

springboot異常(一):springboot自定義全局異常處理

&#x1f337;1. 自定義一個異常類 自定義一個異常&#xff0c;有兩個變量異常代碼、異常消息&#xff0c;定義了兩個構造方法&#xff0c;一個無參構造方法&#xff0c;一個所有參數構造方法。 在構造方法中要掉用父類的構造方法&#xff0c;主要目的是在日志或控制臺打印異…

【Linux】多線程_3

文章目錄 九、多線程3. C11中的多線程4. 線程的簡單封裝 未完待續 九、多線程 3. C11中的多線程 Linux中是根據多線程庫來實現多線程的&#xff0c;C11也有自己的多線程&#xff0c;那它的多線程又是怎樣的&#xff1f;我們來使用一些C11的多線程。 Makefile&#xff1a; te…

Linux - 探索命令行

探索命令行 Linux命令行中的命令使用格式都是相同的: 命令名稱 參數1 參數2 參數3 ...參數之間用任意數量的空白字符分開. 關于命令行, 可以先閱讀一些基本常識. 然后我們介紹最常用的一些命令: ls用于列出當前目錄(即"文件夾")下的所有文件(或目錄). 目錄會用藍色…

面試經典題型:調用HashMap的put方法的具體執行流程

在調用put方法時時&#xff0c;有幾個關鍵點需要考慮&#xff1a; 哈希沖突的發生與解決&#xff1a; 哈希沖突指不同的鍵通過哈希函數計算得到相同的哈希值&#xff0c;導致它們應該存放在哈希表的同一個位置。解決沖突的常用方法包括開放尋址法和鏈表法&#xff08;或其升級形…

CSIP-FTE考試專業題

靶場下載鏈接&#xff1a; https://pan.baidu.com/s/1ce1Kk0hSYlxrUoRTnNsiKA?pwdha1x pte-2003密碼&#xff1a;admin123 centos:root admin123 解壓密碼&#xff1a; PTE考試專用 下載好后直接用vmware打開&#xff0c;有兩個靶機&#xff0c;一個是基礎題&#x…

【CTF-Crypto】數論基礎-02

【CTF-Crypto】數論基礎-02 文章目錄 【CTF-Crypto】數論基礎-021-16 二次剩余1-20 模p下-1的平方根*1-21 Legendre符號*1-22 Jacobi符號*2-1 群*2-2 群的性質2-3 阿貝爾群*2-4 子群2-11 群同態2-18 原根2-21 什么是環2-23 什么是域2-25 子環2-26 理想2-32 多項式環 1-16 二次剩…

打造智慧校園德育管理,提升學生操行基礎分

智慧校園的德育管理系統內嵌的操行基礎分功能&#xff0c;是對學生日常行為規范和道德素養進行量化評估的一個創新實踐。該功能通過將抽象的道德品質轉化為具體可量化的指標&#xff0c;如遵守紀律、尊師重道、團結協作、愛護環境及參與集體活動的積極性等&#xff0c;為每個學…