Spring Boot系列之使用@Scheduled實現定時任務

馮文議發表於2021-10-26

假設,我們有一個資料同步的需求:每隔5秒執行一次資料同步。那麼我們該如何實現這個資料同步任務呢?

哈嘍,大家好,我是小馮。

今天給分享在Spring Boot專案中使用@Scheduled實現定時任務。

快速開始

我們就上面的需求,基於Spring Boot框架,搭建一個簡單的資料同步排程任務。

Demo如下。

建立工程

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

因為我們是基於Spring Boot開發,所以不需要其他依賴。

package com.fengwenyi.demospringbootscheduled;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author <a href="https://www.fengwenyi.com">Erwin Feng</a>
 * @since 2021-09-29
 */
@SpringBootApplication
public class DemoSpringBootScheduledApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoSpringBootScheduledApplication.class, args);
    }

}

啟用排程註解

package com.fengwenyi.demospringbootscheduled.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * @author <a href="https://www.fengwenyi.com">Erwin Feng</a>
 * @since 2021-09-29
 */
@Configuration
@EnableScheduling
public class ScheduledConfiguration {

}

資料同步任務

package com.fengwenyi.demospringbootscheduled.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author <a href="https://www.fengwenyi.com">Erwin Feng</a>
 * @since 2021-10-21
 */
@Component
@Slf4j
public class DemoTask {

    @Scheduled(initialDelay = 5, fixedRate = 5, timeUnit = TimeUnit.SECONDS)
    public void dataSynchronizationTask() {
        log.info("開始執行資料同步任務");
    }

}

執行

通過意思步驟,我們的demo就搭建好了,跑一下,控制檯列印日誌如下:

2021-10-21 21:44:55.711  INFO 10320 --- [   scheduling-1] c.f.d.task.DemoTask                      : 開始執行資料同步任務
2021-10-21 21:45:00.705  INFO 10320 --- [   scheduling-1] c.f.d.task.DemoTask                      : 開始執行資料同步任務
2021-10-21 21:45:05.715  INFO 10320 --- [   scheduling-1] c.f.d.task.DemoTask                      : 開始執行資料同步任務
2021-10-21 21:45:10.710  INFO 10320 --- [   scheduling-1] c.f.d.task.DemoTask                      : 開始執行資料同步任務

通過列印日誌,我們指定,沒間隔5秒,就會自動執行“資料同步任務”,這樣就簡單實現了任務排程。

@Scheduled引數詳解

下面我們對 @Scheduled 註解提供配置,做一個說明。

cron

先看一個例子:每5秒執行一次任務。

@Scheduled(cron = "0/5 * * * * ? ")
public void testCron01() {
    log.info("test cron 01 exec");
}

執行:

2021-10-23 02:31:50.030  INFO 18872 --- [   scheduling-1] c.f.d.task.ScheduledTask                 : test cron 1 exec
2021-10-23 02:31:55.009  INFO 18872 --- [   scheduling-1] c.f.d.task.ScheduledTask                 : test cron 1 exec
2021-10-23 02:32:00.005  INFO 18872 --- [   scheduling-1] c.f.d.task.ScheduledTask                 : test cron 1 exec

關於cron表示式,下面要做幾點說明:

1、結構

 ┌───────────── second (0-59)
 │ ┌───────────── minute (0 - 59)
 │ │ ┌───────────── hour (0 - 23)
 │ │ │ ┌───────────── day of the month (1 - 31)
 │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
 │ │ │ │ │ ┌───────────── day of the week (0 - 7)
 │ │ │ │ │ │          (0 or 7 is Sunday, or MON-SUN)
 │ │ │ │ │ │
 * * * * * *

spring支援的cron表示式,由6位構成,分別表示:

  • 分鐘
  • 小時
  • 天(月)
  • 天(星期)

2、Cron表示式示例

通過閱讀一些cron示例,更能理解cron表示式的具體含義,我們就以spring官方文件中的示例進行學習。

官方文件:https://docs.spring.io/spring...

星號(*)和問號(?)都表示萬用字元,其中,?可以用在 天(月)天(星期)上,即第4位和第6位。

L,表示最後,比如一月最後一個星期天。

W,表示工作日(週一到週五)。

#,表示每月中的第幾個星期幾。5#2:表示每月第2個星期五。MON#1:表示每月第1個星期一。

3、Macros

spring為我們提供了幾個特別的cron表示式(整年,整月,整週,整天或者整夜,整小時),我們可以直接用。

@Scheduled(cron = "@hourly")
public void testCron02() {
    log.info("test cron 02 exec");
}

zone

時區

fixedDelay

固定間隔,引數型別為long。

fixedDelayString

固定間隔,引數型別為String,同fixedDelay。

fixedRate

固定速率,引數型別為long。

fixedRateString

固定速率,引數型別為long,同fixedRate。

timeUnit

時間單位,從 5.3.10開始

spring boot 2.5.5開始

initialDelay

第一次延時時間,引數型別為long。

initialDelayString

第一次延時時間,引數型別為String。

fixedDelay與fixedRate

區別

fixedDelay,間隔時間,以任務結束時間算起。

fixedRate,間隔時間,以任務開始時間算起。

間隔時間大於任務執行時間

比如一個任務,間隔時間為5秒,任務執行時間是2秒。

假設fixedDelay在第5秒執行第一次,那麼第二次會在12秒執行。

而fixedRate在第5秒執行第一次,那麼第二次會在10秒執行。

間隔時間小於任務執行時間

比如一個任務,間隔時間為2秒,任務執行時間是5秒。

假設fixedDelay在第2秒執行第一次,那麼第二次會在9秒執行。

而fixedRate在第2秒執行第一次,那麼第二次會在7秒執行。

配置檔案

在實際專案中,執行時間一般寫在配置檔案中,方便修改,不然,如果要修改,還要改程式碼。

關於如何寫在配置檔案中,相信你一定遇到過這個問題。

這部分我們解決這樣一個問題,並進行總結。

cron

@Scheduled(cron = "${erwin.cron:0/2 * * * * ?}")
public void cronTaskYmlDemo() {
    log.info("cron yml demo");
}

配置:

erwin:
  cron: 0/10 * * * * ?

如果配置檔案沒有配,就會使用預設的值。

請注意,值為空,不等於沒有配。

fixedDelay

在上面引數解釋的時候,我們指定,這個接收的是一個整數,那該如何將解決這個問題。

相信聰明的你,一定也是猜到了。

對,沒錯,就是它。

@Scheduled(initialDelay = 5, fixedDelayString = "${erwin.fixed-delay:2}", timeUnit = TimeUnit.SECONDS)
public void fixedDelayTaskYmlDemo() {
    log.info("fixedDelay yml demo");
}

配置:

erwin:
  fixed-delay: 5

簡單解釋一下,如果在配置檔案中沒有配置,則每隔2秒執行一次,如果配置了,就每隔5秒執行一次。initialDelay 表示,專案啟動後,5秒開始執行第一次任務。

值得注意的是,${erwin.fixed-delay:2},冒號前後不能有空格。

fixedRate

有了上面的經驗,相信你一定學會了。我們一起來看示例吧。

@Scheduled(initialDelay = 5, fixedRateString = "${erwin.fixed-rate:2}", timeUnit = TimeUnit.SECONDS)
public void fixedRateTaskYmlDemo() {
    log.info("fixedRate yml demo");
}

配置:

erwin:
  fixed-rate: 5

執行示例:

2021-10-25 20:41:57.394  INFO 19368 --- [   scheduling-1] c.f.d.task.DemoTask                      : fixedRate yml demo
2021-10-25 20:41:59.394  INFO 19368 --- [   scheduling-1] c.f.d.task.DemoTask                      : fixedRate yml demo
2021-10-25 20:42:01.394  INFO 19368 --- [   scheduling-1] c.f.d.task.DemoTask                      : fixedRate yml demo

最後的最後,還有一個問題,先看圖。

發現問題了嗎?

我們在寫配置的時候,沒有提示,並且這種看上去,也不友好。

那要怎麼解決呢?

先引入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

我們不妨寫一個屬性配置類。

@Getter
@Setter
@Configuration
@ConfigurationProperties("erwin")
public class ErwinProperties {

    private String cron;

    private Long fixedDelay;

    private Long fixedRate;

}

你注意到 erwin 這個了嗎?

剛開始寫示例的時候,你是不是很好奇,為什麼會有這個字首

哈哈,其實我們早已埋下了伏筆。

最後,再來看看吧。

同時,這時候,你再寫配的時候,就會有提示了。

今天分享的內容,就是這些了,我們們下期再見!

相關文章