spring - mvc - @Scheduled

dkpp發表於2024-03-07

@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

相關文章