@Scheduled
1.啟用排程支援
為了在Spring中啟用對排程任務和@Scheduled註釋的支援,我們可以使用Java啟用樣式註釋:
@Configuration
@EnableScheduling
public class SpringConfig {
...
}
相反,我們可以在 XML 中做同樣的事情:
<task:annotation-driven>
2.按固定延遲安排任務
讓我們首先將任務配置為在固定延遲後執行:
@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
System.out.println(
"Fixed delay task - " + System.currentTimeMillis() / 1000);
}
在這種情況下,上一次執行結束和下一次執行開始之間的持續時間是固定的。該任務總是等待,直到前一個任務完成。
當必須在再次執行之前完成先前的執行時,應使用此選項。
3.以固定速率安排任務
現在讓我們以固定的時間間隔執行任務:
@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);
}
}
現在,即使前一個任務尚未完成,該非同步任務也會每秒呼叫一次。
4.固定速率與固定延遲
我們可以使用 Spring 的@Scheduled註解來執行計劃任務,但是根據屬性fixedDelay 和 fixedRate, 執行的性質會發生變化。
fixedDelay屬性確保任務執行的完成時間和下一次任務執行的開始時間之間有 n 毫秒的延遲。
當我們需要確保始終只有一個任務例項執行時,此屬性特別有用。對於依賴工作來說,這是相當有幫助的。
fixedRate屬性每 n毫秒執行一次計劃任務。它不檢查該任務的任何先前執行情況。
當任務的所有執行都是獨立的時,這非常有用。如果我們不希望超出記憶體和執行緒池的大小,fixedRate 應該會很方便。
儘管如此,如果傳入的任務沒有快速完成,它們最終可能會出現“記憶體不足異常”。
5.安排一個帶有初始延遲的任務
接下來,讓我們安排一個有延遲(以毫秒為單位)的任務:
@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);
}
請注意在此示例中我們如何同時使用fixedDelay和initialDelay。任務會在initialDelay值之後第一次執行,並且會按照fixedDelay繼續執行。
當任務有需要完成的設定時,此選項很方便
6.使用 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 執行。
7.引數化時間表
對這些時間表進行硬編碼很簡單,但我們通常需要能夠控制時間表,而無需重新編譯和重新部署整個應用程式。
我們將使用 Spring 表示式來外部化任務的配置,並將它們儲存在屬性檔案中。
// 固定延遲任務
@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}"):
// 固定速率任務:
@Scheduled(fixedRateString = "${fixedRate.in.milliseconds}")
// 基於cron表示式的任務:
@Scheduled(cron = "${cron.expression}")
8.使用 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>
9.在執行時動態設定延遲或速率
通常, @Scheduled註解的所有屬性僅在 Spring 上下文啟動時解析和初始化一次。
因此,當我們在 Spring 中使用@Scheduled註解時,不可能在執行時更改fixedDelay或fixedRate值。
不過,有一個解決方法。使用Spring的SchedulingConfigurer提供了一種更加可定製的方式,使我們有機會動態設定延遲或速率。
讓我們建立一個 Spring 配置DynamicSchedulingConfig並實現SchedulingConfigurer介面:
@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方法動態確定的。
10.並行執行任務
預設情況下,Spring 使用本地單執行緒排程程式來執行任務。因此,即使我們有多個@Scheduled方法,它們每個都需要等待執行緒完成執行上一個任務。
如果我們的任務真正獨立,那麼並行執行它們會更方便。為此,我們需要提供一個更適合我們需求的TaskScheduler :
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
return threadPoolTaskScheduler;
}
在上面的示例中,我們將TaskScheduler配置為池大小為 5,但請記住,實際配置應根據具體需求進行微調。
11.使用 Spring Boot
如果我們使用 Spring Boot,我們可以使用更方便的方法來增加排程程式池的大小。
設定spring.task.scheduling.pool.size屬性就足夠了:
spring.task.scheduling.pool.size=5