一文帶你掌握SpringBoot中常見定時(shí)任務(wù)的實(shí)現(xiàn)
引言
在現(xiàn)代軟件開發(fā)中,定時(shí)任務(wù)是一種常見的需求,用于執(zhí)行周期性的任務(wù)或在特定的時(shí)間點(diǎn)執(zhí)行任務(wù)。這些任務(wù)可能涉及數(shù)據(jù)同步、數(shù)據(jù)備份、報(bào)表生成、緩存刷新等方面,對系統(tǒng)的穩(wěn)定性和可靠性有著重要的影響。Spring Boot
提供了強(qiáng)大且簡單的定時(shí)任務(wù)功能,使開發(fā)人員能夠輕松地管理和執(zhí)行這些任務(wù)。
本文將介紹 Spring Boot
中定時(shí)任務(wù)的基本用法、高級特性以及最佳實(shí)踐,幫助開發(fā)人員更好地理解和應(yīng)用定時(shí)任務(wù),提高系統(tǒng)的穩(wěn)定性和可靠性。
SpringBoot中的定時(shí)任務(wù)
SpringBoot
中的定時(shí)任務(wù)主要通過@Scheduled
注解以及SchedulingConfigurer
接口實(shí)現(xiàn)。
@Scheduled注解
@Scheduled
注解是Spring
提供的一個(gè)注解,用于標(biāo)記方法作為定時(shí)任務(wù)執(zhí)行。通過 @Scheduled
注解,開發(fā)人員可以輕松地配置方法在指定的時(shí)間間隔或時(shí)間點(diǎn)執(zhí)行,實(shí)現(xiàn)各種定時(shí)任務(wù)需求。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { String cron() default ""; long fixedDelay() default -1; long fixedRate() default -1; long initialDelay() default -1; }
以上為@Scheduled
源碼中關(guān)鍵屬性,各屬性含義如下:
cron: 接受標(biāo)準(zhǔn)的Unix Cron表達(dá)式,用于定義復(fù)雜的計(jì)劃執(zhí)行時(shí)間。
/** * cron屬性可以設(shè)置指定時(shí)間執(zhí)行,cron表達(dá)式跟linux一樣 */ @Scheduled(cron = "0 45 14 ? * *") public void fixTimeExecution() { System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行"); }
fixedRate: 以固定的頻率執(zhí)行任務(wù),指定兩次執(zhí)行之間的間隔時(shí)間(單位是毫秒)。
/** * fixedRate屬性設(shè)置每隔固定時(shí)間執(zhí)行 */ @Scheduled(fixedRate = 5000) public void reportCurrentTime() { System.out.println("每隔五秒執(zhí)行一次" + dateFormat.format(new Date())); }
fixedDelay:在每次任務(wù)完成后等待一定的時(shí)間再進(jìn)行下一次執(zhí)行,指定連續(xù)執(zhí)行之間的延遲時(shí)間。
/** * 上一次任務(wù)執(zhí)行完成之后10秒后在執(zhí)行 */ @Scheduled(fixedDelay = 10000) public void runWithFixedDelay() { System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行"); }
initialDelay:首次執(zhí)行前的延遲時(shí)間。
/** * 初始延遲1秒后開始,然后每10秒執(zhí)行一次 */ @Scheduled(initialDelay=1000, fixedDelay=10000) public void executeWithInitialAndFixedDelay() { System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行"); }
這里要注意fixedRate與fixedDelay的區(qū)別:fixedRate
是基于任務(wù)開始執(zhí)行的時(shí)間點(diǎn)來計(jì)算下一次任務(wù)開始執(zhí)行的時(shí)間,因此任務(wù)的執(zhí)行時(shí)間間隔是相對固定的,不受到任務(wù)執(zhí)行時(shí)間的影響。如果指定的時(shí)間間隔小于任務(wù)執(zhí)行的實(shí)際時(shí)間,則任務(wù)可能會并發(fā)執(zhí)行。而fixedDelay
是基于任務(wù)執(zhí)行完成的時(shí)間點(diǎn)來計(jì)算下一次任務(wù)開始執(zhí)行的時(shí)間,因此任務(wù)的執(zhí)行時(shí)間間隔是相對不規(guī)則的,受到任務(wù)執(zhí)行時(shí)間的影響。
SpringBoot
支持同時(shí)定義多個(gè)定時(shí)任務(wù)方法,每個(gè)方法可以使用不同的參數(shù)配置,以滿足不同的定時(shí)任務(wù)需求。同時(shí),我們必須在配置類中使用@EnableScheduling
注解開啟定時(shí)任務(wù)。
@Configuration @EnableScheduling public class ScheduledTaskConfig { }
或者
@EnableScheduling @SpringBootApplication public class SpringBootBaseApplication { public static void main(String[] args) { SpringApplication.run(SpringBootBaseApplication.class, args); } }
在SpringBoot應(yīng)用程序中,除了在代碼中使用注解配置定時(shí)任務(wù)外,還可以通過配置文件來配置定時(shí)任務(wù)的執(zhí)行規(guī)則。這種方式更加靈活,可以在不修改源代碼的情況下,動(dòng)態(tài)調(diào)整定時(shí)任務(wù)的執(zhí)行規(guī)則。比如我們在application.properties
中配置@Scheduled
的屬性:
custom.scheduled.cron = 0/5 * * * * ? custom.scheduled.fixedRate=5000 custom.scheduled.fixedDelay=10000 custom.scheduled.initialDelay=1000
然后在@Scheduled
的方法使用屬性配置定時(shí)任務(wù)執(zhí)行頻率。
@Service public class DemoScheduledTaskService { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); /** * fixedRate屬性設(shè)置每隔固定時(shí)間執(zhí)行 */ @Scheduled(fixedRateString = "${custom.scheduled.fixedRate}") public void reportCurrentTime() { System.out.println("每隔五秒執(zhí)行一次" + dateFormat.format(new Date())); } /** * cron屬性可以設(shè)置指定時(shí)間執(zhí)行,cron表達(dá)式跟linux一樣 */ @Scheduled(cron = "${custom.scheduled.cron}") public void fixTimeExecution() { System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行"); } /** * 上一次任務(wù)執(zhí)行完成之后10秒后在執(zhí)行 */ @Scheduled(fixedDelayString = "${custom.scheduled.fixedDelay}") public void runWithFixedDelay() { System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行"); } /** * 初始延遲1秒后開始,然后每10秒執(zhí)行一次 */ @Scheduled(initialDelayString = "${custom.scheduled.initialDelay}", fixedDelayString = "${custom.scheduled.fixedDelay}") public void executeWithInitialAndFixedDelay() { System.out.println("指定時(shí)間 "+dateFormat.format(new Date())+"執(zhí)行"); } }
注意,這里使用屬性來指定任務(wù)執(zhí)行頻率時(shí),要通過@Scheduled的fixedRateString、fixedDelayString、initialDelayString三個(gè)可以指定字符串的值的屬性去指定,效果等同于long類型的屬性。
通過配置文件配置定時(shí)任務(wù)具有很高的靈活性,可以在不重新編譯和部署應(yīng)用程序的情況下,隨時(shí)調(diào)整定時(shí)任務(wù)的執(zhí)行規(guī)則。同時(shí),也可以根據(jù)不同的環(huán)境(例如開發(fā)、測試、生產(chǎn))配置不同的定時(shí)任務(wù)規(guī)則,以滿足不同環(huán)境下的需求。這種方式可以有效地解耦定時(shí)任務(wù)的配置和業(yè)務(wù)代碼,提高系統(tǒng)的靈活性和可維護(hù)性。
另外,如果希望定時(shí)任務(wù)能夠異步執(zhí)行,不阻塞主線程,可以在方法上同時(shí)加上@Async
注解,這樣各任務(wù)就可以異步執(zhí)行了。有關(guān)SpringBoot中使用@Async
的講解,請移步:
雖然 @Scheduled
注解是一個(gè)方便的方式來定義定時(shí)任務(wù),但它也存在一些弊端。因?yàn)槿蝿?wù)的執(zhí)行計(jì)劃(如cron表達(dá)式)在編譯時(shí)被硬編碼,因此無法在運(yùn)行時(shí)動(dòng)態(tài)修改,除非重新部署。此外,@Scheduled注解對于配置不同的調(diào)度策略(如使用不同的線程池)顯得力不從心,而且默認(rèn)情況下,@Scheduled任務(wù)在單線程環(huán)境下執(zhí)行,可能出現(xiàn)任務(wù)堆積的情況,尤其在任務(wù)量大或任務(wù)執(zhí)行時(shí)間長的情況下,而且這些任務(wù)可能會變得混亂和難以管理。定時(shí)任務(wù)的配置分散在各個(gè)任務(wù)方法中,不利于統(tǒng)一管理和維護(hù)。對于需要根據(jù)動(dòng)態(tài)條件創(chuàng)建或銷毀定時(shí)任務(wù)的情況,@Scheduled
注解也無法滿足需求。
為了解決這些問題,可以使用SchedulingConfigurer
接口來動(dòng)態(tài)地創(chuàng)建和管理定時(shí)任務(wù)。通過實(shí)現(xiàn) SchedulingConfigurer
接口,我們可以編寫代碼來動(dòng)態(tài)地注冊和管理定時(shí)任務(wù),從而實(shí)現(xiàn)靈活的任務(wù)調(diào)度需求。接下來,我們將介紹如何使用SchedulingConfigurer
接口來創(chuàng)建定時(shí)任務(wù)。
SchedulingConfigurer接口
SchedulingConfigurer
接口是 Spring 提供的一個(gè)用于定時(shí)任務(wù)配置的擴(kuò)展接口,它允許開發(fā)人員更細(xì)粒度地控制定時(shí)任務(wù)的執(zhí)行。通過實(shí)現(xiàn)SchedulingConfigurer
接口,可以自定義任務(wù)調(diào)度器(TaskScheduler
),配置線程池等參數(shù),以滿足不同場景下的定時(shí)任務(wù)需求。
@Configuration @EnableScheduling public class CustomSchedulingConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 定時(shí)任務(wù)邏輯 } }
通過實(shí)現(xiàn)SchedulingConfigurer
接口,重寫configureTasks
方法,自定義任務(wù)調(diào)度器的配置。此外我們還可以配置線程池,用于控制定時(shí)任務(wù)執(zhí)行時(shí)的線程數(shù)量、并發(fā)性等參數(shù)。
@Bean(destroyMethod = "shutdown") public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); // 設(shè)置線程池大小 scheduler.setThreadNamePrefix("scheduled-task-"); // 設(shè)置線程名稱前綴 scheduler.setAwaitTerminationSeconds(60); // 設(shè)置終止等待時(shí)間 // 設(shè)置處理拒絕執(zhí)行的任務(wù)異常 scheduler.setRejectedExecutionHandler((r, executor) -> log.error("Task rejected", r)); // 處理定時(shí)任務(wù)執(zhí)行過程中拋出的未捕獲異常 scheduler.setErrorHandler(e -> log.error("Error in scheduled task", e)); return scheduler; }
然后將自定義的ThreadPoolTaskScheduler
設(shè)置到ScheduledTaskRegistrar
中去:
@Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 定時(shí)任務(wù)邏輯 taskRegistrar.setTaskScheduler(threadPoolTaskScheduler()); }
有關(guān)線程池的配置參數(shù)講解,請移步:
通過SchedulingConfigurer
接口,可以更靈活地配置任務(wù)調(diào)度器和定時(shí)任務(wù)的執(zhí)行規(guī)則,比如動(dòng)態(tài)注冊定時(shí)任務(wù)、動(dòng)態(tài)修改任務(wù)執(zhí)行規(guī)則等。
動(dòng)態(tài)添加定時(shí)任務(wù)
在SchedulingConfigurer
的configureTasks
方法中,我們可以根據(jù)業(yè)務(wù)需求,從數(shù)據(jù)庫、配置文件或其它動(dòng)態(tài)來源獲取定時(shí)任務(wù)的信息(如Cron表達(dá)式、任務(wù)執(zhí)行類等),然后創(chuàng)建對應(yīng)的Runnable
或Callable
實(shí)例,并結(jié)合Trigger
(如CronTrigger
)將其添加到調(diào)度器中。相比@Scheduled
注解,這種方式能夠在應(yīng)用運(yùn)行時(shí)隨時(shí)添加新的定時(shí)任務(wù)。
@Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ThreadPoolTaskScheduler scheduler = threadPoolTaskScheduler(); taskRegistrar.setTaskScheduler(scheduler); List<CronTaskInfo> tasksFromDB = listTasksFromDatabase(); for (CronTaskInfo task : tasksFromDB) { Runnable taskRunner = new MyTaskExecutor(task.getTaskData()); CronTrigger cronTrigger = new CronTrigger(task.getCronExpression()); scheduler.schedule(taskRunner, cronTrigger); } }
關(guān)于這里在應(yīng)用運(yùn)行時(shí),動(dòng)態(tài)的添加新的任務(wù),我們可以通過事件驅(qū)動(dòng),輪訓(xùn)檢查,消息隊(duì)列等多種方式,監(jiān)聽到數(shù)據(jù)庫中或者配置文件中新增任務(wù)信息,然后通過SchedulingConfigurer
接口動(dòng)態(tài)創(chuàng)建定時(shí)任務(wù)。而這種方式是@Scheduled注解做不到的。
修改定時(shí)任務(wù)規(guī)則
當(dāng)任務(wù)的執(zhí)行規(guī)則需要?jiǎng)討B(tài)變更時(shí),同樣可以在configureTasks
方法中實(shí)現(xiàn)。例如,從數(shù)據(jù)庫獲取最新的Cron表達(dá)式,然后取消當(dāng)前任務(wù)并重新添加新的任務(wù)實(shí)例。需要注意的是,取消已有任務(wù)通常需要持有對該任務(wù)的引用,例如使用Scheduler
提供的unschedule
方法。
// 假設(shè)我們有一個(gè)方法用于獲取更新后的任務(wù)信息 CronTaskInfo updatedTask = getUpdatedTaskInfoFromDatabase(); // 取消舊的任務(wù)(需要知道舊任務(wù)的TriggerKey) TriggerKey triggerKey = ...; // 獲取舊任務(wù)的TriggerKey scheduler.unschedule(triggerKey); // 創(chuàng)建新任務(wù)并設(shè)置新的Cron表達(dá)式 MyTaskExecutor taskExecutor = new MyTaskExecutor(updatedTask.getTaskData()); CronTrigger updatedCronTrigger = new CronTrigger(updatedTask.getCronExpression()); // 重新調(diào)度新任務(wù) scheduler.schedule(taskRunner, updatedCronTrigger);
另外,我們還可以通過添加任務(wù)時(shí)對其排序或設(shè)置優(yōu)先級等方式間接實(shí)現(xiàn)設(shè)置定時(shí)任務(wù)的執(zhí)行順序。
通過實(shí)現(xiàn)SchedulingConfigurer
接口,我們可以擁有對定時(shí)任務(wù)調(diào)度的更多控制權(quán),比如自定義線程池、動(dòng)態(tài)添加任務(wù)以及調(diào)整任務(wù)執(zhí)行策略。這種靈活性使得在復(fù)雜環(huán)境下,特別是需要?jiǎng)討B(tài)管理定時(shí)任務(wù)時(shí),SchedulingConfigurer
成為了理想的選擇。
其他第三方任務(wù)調(diào)度框架
除了使用Spring框架提供的 @Scheduled
注解和SchedulingConfigurer
接口外,還有許多第三方的任務(wù)調(diào)度庫可供選擇。這些庫通常提供了更多的功能和靈活性,以滿足各種復(fù)雜的任務(wù)調(diào)度需求。以下是一些常見的第三方任務(wù)調(diào)度庫:
Quartz Scheduler:
Quartz
是一個(gè)功能強(qiáng)大且靈活的任務(wù)調(diào)度庫,具有豐富的功能,如支持基于cron
表達(dá)式的任務(wù)調(diào)度、集群支持、作業(yè)持久化等。它可以與Spring
框架集成,并且被廣泛應(yīng)用于各種類型的任務(wù)調(diào)度應(yīng)用程序中。
Elastic Job:
Elastic Job
是一個(gè)分布式任務(wù)調(diào)度框架,可以輕松實(shí)現(xiàn)分布式任務(wù)調(diào)度和作業(yè)執(zhí)行。它提供了分布式任務(wù)執(zhí)行、作業(yè)依賴關(guān)系、作業(yè)分片等功能,適用于大規(guī)模的分布式任務(wù)調(diào)度場景。
xxl-job:
xxl-job
是一個(gè)分布式任務(wù)調(diào)度平臺,提供了可視化的任務(wù)管理界面和多種任務(wù)調(diào)度方式,如單機(jī)任務(wù)、分布式任務(wù)、定時(shí)任務(wù)等。它支持任務(wù)執(zhí)行日志、任務(wù)失敗重試、動(dòng)態(tài)調(diào)整任務(wù)執(zhí)行策略等功能。
PowerJob:
PowerJob
是一個(gè)開源的分布式任務(wù)調(diào)度框架,由阿里巴巴集團(tuán)開發(fā)并開源。PowerJob 提供了分布式、高可用的任務(wù)調(diào)度能力,支持多種任務(wù)類型,如定時(shí)任務(wù)、延時(shí)任務(wù)、流程任務(wù)等。
總結(jié)
定時(shí)任務(wù)在現(xiàn)代軟件開發(fā)中扮演著重要的角色,它們可以自動(dòng)化執(zhí)行各種重復(fù)性的任務(wù),提高系統(tǒng)的效率和可靠性。SpringBoot提供了強(qiáng)大而靈活的定時(shí)任務(wù)功能,使我們能夠輕松地管理和執(zhí)行各種定時(shí)任務(wù)。通過@Scheduled
注解和SchedulingConfigurer
接口,我們可以根據(jù)需求配置定時(shí)任務(wù)的執(zhí)行規(guī)則,實(shí)現(xiàn)各種復(fù)雜的定時(shí)任務(wù)調(diào)度需求。我們可以充分利用SpringBoot中的定時(shí)任務(wù)功能,提高系統(tǒng)的穩(wěn)定性和可靠性,從而更好地滿足業(yè)務(wù)需求。
到此這篇關(guān)于一文帶你掌握SpringBoot中常見定時(shí)任務(wù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot定時(shí)任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 利用SpringBoot解決多個(gè)定時(shí)任務(wù)阻塞的問題
- SpringBoot定時(shí)任務(wù)的實(shí)現(xiàn)詳解
- SpringBoot中定時(shí)任務(wù)的使用方法解析
- Springboot實(shí)現(xiàn)定時(shí)任務(wù)的4種方式舉例詳解
- Springboot項(xiàng)目中定時(shí)任務(wù)的四種實(shí)現(xiàn)方式詳解
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)增刪啟停定時(shí)任務(wù)的方式
- SpringBoot之解決多個(gè)定時(shí)任務(wù)阻塞的問題
- SpringBoot創(chuàng)建定時(shí)任務(wù)的示例詳解
- SpringBoot自定義定時(shí)任務(wù)的實(shí)現(xiàn)示例
相關(guān)文章
Java導(dǎo)入、導(dǎo)出excel用法步驟保姆級教程(附封裝好的工具類)
這篇文章主要介紹了Java導(dǎo)入、導(dǎo)出excel的相關(guān)資料,講解了使用Java和ApachePOI庫將數(shù)據(jù)導(dǎo)出為Excel文件,包括創(chuàng)建工作簿、工作表、行和單元格,設(shè)置樣式和字體,合并單元格,添加公式和下拉選擇等功能,需要的朋友可以參考下2025-03-03Java中資源加載的方法及Spring的ResourceLoader應(yīng)用小結(jié)
在Java開發(fā)中,資源加載是一個(gè)基礎(chǔ)而重要的操作,這篇文章主要介紹了深入理解Java中資源加載的方法及Spring的ResourceLoader應(yīng)用,本文通過實(shí)例代碼演示了通過ClassLoader和Class獲取資源的內(nèi)容,以及使用Spring的ResourceLoader加載多個(gè)資源的過程,需要的朋友可以參考下2024-01-01Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算
這篇文章主要為大家介紹了Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06springcloud項(xiàng)目快速開始起始模板的實(shí)現(xiàn)
本文主要介紹了springcloud項(xiàng)目快速開始起始模板思路的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12