1. 概述
本教程將演示如何使用 Spring 的 @Scheduled 註解來配置和安排任務。
以下是標註方法使用 @Scheduled 註解的簡單規則:
- 方法通常應具有 `void` 返回類型(如果不是,返回的值將被忽略)
- 方法不應接受任何參數
2. 啓用任務調度支持
為了啓用 Spring 中任務調度和 @Scheduled 註解的支持,我們可以使用 Java 啓用風格的註解:
@Configuration
@EnableScheduling
public class SpringConfig {
...
}相反,我們也可以在XML中做同樣的事情:
<task:annotation-driven>3. 安排固定延遲任務
讓我們從配置一個在固定延遲後運行的任務開始:
@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
System.out.println(
"Fixed delay task - " + System.currentTimeMillis() / 1000);
}在這種情況下,上一次執行結束與下一次執行開始之間的持續時間是固定的。任務始終等待前一個任務完成。
此選項應在必須在再次運行之前完成先前執行時使用。
4. 定時安排任務
現在,讓我們以固定的時間間隔執行一個任務:
@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
System.out.println(
"Fixed rate task - " + System.currentTimeMillis() / 1000);
}此選項應在每個任務執行時保持獨立性時使用。
請注意,按計劃執行的任務默認情況下不會並行運行。即使我們使用了 fixedRate,下一次任務也不會在先前任務完成之後被調用。
為了在按計劃執行的任務中支持並行行為,我們需要添加 @Async 註解:
@EnableAsync
public class ScheduledFixedRateExample {
@Async
@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTaskAsync() throws InterruptedException {
System.out.println(
"Fixed rate task async - " + System.currentTimeMillis() / 1000);
Thread.sleep(2000);
}
}現在,這個異步任務將會在每秒被調用,即使上一個任務尚未完成。
5. 定速模式與延時模式
我們可以使用 Spring 的 <em @Scheduled</em> 註解來運行計劃任務,但根據 fixedDelay 和 fixedRate 屬性,執行的性質會發生改變。
fixedDelay 屬性確保在任務執行完成時間和下一個任務執行開始時間之間,有 n 毫秒的延遲。
該屬性在我們需要確保同一時間只有一個任務實例運行的場景中特別有用,對於依賴關係的任務也很有幫助。
fixedRate 屬性則每執行 n 毫秒就運行一次計劃任務。它不會檢查任何先前執行任務的情況。
這在所有任務執行都是獨立的場景中非常有用。如果預計內存和線程池大小不會超出限制,fixedRate 應該非常方便。
然而,如果傳入的任務沒有快速完成,則可能導致“Out of Memory 異常”。
安排帶有初始延遲的任務
接下來,讓我們安排一個帶有延遲(以毫秒為單位)的任務:
@Scheduled(fixedDelay = 1000, initialDelay = 1000)
public void scheduleFixedRateWithInitialDelayTask() {
long now = System.currentTimeMillis() / 1000;
System.out.println(
"Fixed rate task with one second initial delay - " + now);
}請注意我們在此示例中同時使用了固定延遲以及初始延遲。任務將在初始延遲值之後首次執行,並根據固定延遲繼續執行。
此選項在任務需要完成設置時非常方便。
7. 使用 Cron 表達式安排任務
有時延遲和速率不足以滿足需求,我們需要使用 Cron 表達式來控制任務的調度:
@Scheduled(cron = "0 15 10 15 * ?")
public void scheduleTaskUsingCronExpression() {
long now = System.currentTimeMillis() / 1000;
System.out.println(
"schedule tasks using cron jobs - " + now);
}請注意,在這個示例中,我們將安排一個任務在每個月的第 15 天的上午 10:15 執行。
默認情況下,Spring 將使用服務器的本地時區對 cron 表達式進行解析。但是,我們可以使用 zone 屬性來更改時區:
@Scheduled(cron = "0 15 10 15 * ?", zone = "Europe/Paris")使用此配置,Spring 將在巴黎時間每月第 15 日的上午 10:15 安排運行該註解方法。
8. 參數化調度
硬編碼這些調度方案雖然簡單,但我們通常需要能夠在不重新編譯和部署整個應用程序的情況下控制調度。
我們將利用 Spring 表達式來外部化任務的配置,並將這些配置存儲在屬性文件中。
一個 固定延遲 任務:
@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}")一項固定利率的任務:
@Scheduled(fixedRateString = "${fixedRate.in.milliseconds}")一個基於 cron 表達式的任務:
@Scheduled(cron = "${cron.expression}")9. 使用 XML 配置計劃任務
Spring 還提供了使用 XML 的方式來配置計劃任務。以下是用於設置這些任務的 XML 配置:
<!-- Configure the scheduler -->
<task:scheduler id="myScheduler" pool-size="10" />
<!-- Configure parameters -->
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA"
fixed-delay="5000" initial-delay="1000" />
<task:scheduled ref="beanB" method="methodB"
fixed-rate="5000" />
<task:scheduled ref="beanC" method="methodC"
cron="*/5 * * * * MON-FRI" />
</task:scheduled-tasks>10. 運行時動態設置延遲或頻率
正常情況下,所有<em @Scheduled</em> 註解的屬性都會在 Spring 容器啓動時僅解析和初始化一次。
因此,當我們使用 <em @Scheduled</em> 註解在 Spring 中時,無法通過更改 fixedDelay 或 fixedRate 值來在運行時進行調整。
但是,存在一種解決方法。 <em SchedulingConfigurer</em> 提供了更靈活的方式,讓我們有機會在運行時動態設置延遲或頻率。
讓我們創建一個 Spring 配置,名為 <em DynamicSchedulingConfig</em>>,並實現 <em SchedulingConfigurer</em>> 接口:
@Configuration
@EnableScheduling
public class DynamicSchedulingConfig implements SchedulingConfigurer {
@Autowired
private TickService tickService;
@Bean
public Executor taskExecutor() {
return Executors.newSingleThreadScheduledExecutor();
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new Runnable() {
@Override
public void run() {
tickService.tick();
}
},
new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext context) {
Optional<Date> lastCompletionTime =
Optional.ofNullable(context.lastCompletionTime());
Instant nextExecutionTime =
lastCompletionTime.orElseGet(Date::new).toInstant()
.plusMillis(tickService.getDelay());
return Date.from(nextExecutionTime);
}
}
);
}
}如我們所見,通過使用 ScheduledTaskRegistrar#addTriggerTask 方法,我們可以添加一個 Runnable 任務和一個 Trigger 實現,從而在每次執行結束後重新計算 nextExecutionTime。
此外,我們使用 @EnableScheduling 註解來配置我們的 DynamicSchedulingConfig,以便啓用調度功能。
因此,我們安排了 TickService#tick 方法在每次延遲量確定後執行,該延遲量在運行時由 getDelay 方法動態計算。
11. 並行運行任務
默認情況下,Spring 使用本地單線程調度器運行任務。因此,即使我們有多個 @Scheduled 方法,它們也需要等待前一個任務執行完畢。
如果我們的任務是真正獨立的,則在並行運行它們更方便。為此,我們需要提供一個 TaskScheduler,以滿足我們的需求:
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
return threadPoolTaskScheduler;
}在上述示例中,我們配置了 TaskScheduler 的池大小為五個,但請記住,實際配置應根據具體需求進行調整。
11.1. 使用 Spring Boot
如果使用 Spring Boot,我們可以採用更便捷的方法來增加調度器池的大小。
只需設置 spring.task.scheduling.pool.size 屬性:
spring.task.scheduling.pool.size=5
12. 結論
在本文中,我們討論瞭如何配置和使用 @Scheduled 註解。
我們介紹了啓用排程的流程,以及各種配置排程任務模式的方法。我們還展示了一種配置延遲和速率的動態解決方案。