Spring Boot 定時任務 -- @Scheduled


Spring Framework 自身提供了對定時任務的支持,本文介紹 Spring Boot 中 @Scheduled 定時器的使用。

首先,在項目啟動類上添加 @EnableScheduling 注解,開啟對定時任務的支持

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class DemoSpringBootScheduledApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoSpringBootScheduledApplication.class, args);
    }
}
其次,編寫定時任務類和方法,定時任務類通過 Spring IOC 加載,使用 @Component 注解(當然也可以使用 @Controller@Service 等其他與 @Component 作用相同的注解),定時方法使用 @Scheduled 注解。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class ScheduledTask {

    @Scheduled(fixedRate = 3000)//每三秒執行一次
    public void scheduledTask() {
        System.out.println("Task executed at " + LocalDateTime.now());
    }
}

注意以上代碼使用了 @ScheduledfixedRate 屬性,fixedRatelong 類型,表示任務執行的間隔毫秒數,以上代碼中的定時任務每 3 秒執行一次。

運行定時工程,項目啟動和運行日志如下,可見每 3 秒打印一次日志執行記錄。

2018-07-25 20:49:29.610  INFO 11060 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Starting DemoSpringBootScheduledApplication on LAPTOP-C375ASPB with PID 11060 (D:\JYL\DEV\IdeaProjects\demo\demo-spring-boot-scheduled\target\classes started by Ji in D:\JYL\DEV\IdeaProjects\demo)
2018-07-25 20:49:29.614  INFO 11060 --- [           main] s.b.s.DemoSpringBootScheduledApplication : No active profile set, falling back to default profiles: default
2018-07-25 20:49:29.671  INFO 11060 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@345965f2: startup date [Wed Jul 25 20:49:29 CST 2018]; root of context hierarchy
2018-07-25 20:49:30.749  INFO 11060 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-07-25 20:49:30.766  INFO 11060 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-25 20:49:30.791  INFO 11060 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Started DemoSpringBootScheduledApplication in 1.516 seconds (JVM running for 2.051)
Task executed at 2018-07-25T20:49:30.791
Task executed at 2018-07-25T20:49:33.780
Task executed at 2018-07-25T20:49:36.778
......

配置詳解查看 @Scheduled 源碼(基於 Spring Boot 2.0.3.RELEASE 版本依賴)

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String cron() default "";

    String zone() default "";

    long fixedDelay() default -1L;

    String fixedDelayString() default "";

    long fixedRate() default -1L;

    String fixedRateString() default "";

    long initialDelay() default -1L;

    String initialDelayString() default "";
}

共支持 8 種配置:
1 cron
Cron(計划任務)表達式廣泛應用於各種定時解決方案,參考 Cron 表達式詳解

2 zone
用於解析 Cron 表達式的時區

3 fixedDelay
上次調用結束和下一次調用結束之間的固定周期(單位:毫秒),即上一次執行完畢時間點之后延遲執行。

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class ScheduledTask {

    @Scheduled(fixedDelay = 3000)
    public void scheduledTask() {
        System.out.println("Task executed at " + LocalDateTime.now());
    }
}

運行定時工程,項目啟動和運行日志如下,可見每 3 秒打印一次日志執行記錄

2018-07-29 11:08:04.406  INFO 10436 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Starting DemoSpringBootScheduledApplication on LAPTOP-C375ASPB with PID 10436 (D:\JYL\DEV\IdeaProjects\demo\demo-spring-boot-scheduled\target\classes started by Ji in D:\JYL\DEV\IdeaProjects\demo)
2018-07-29 11:08:04.411  INFO 10436 --- [           main] s.b.s.DemoSpringBootScheduledApplication : No active profile set, falling back to default profiles: default
2018-07-29 11:08:04.468  INFO 10436 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@70b0b186: startup date [Sun Jul 29 11:08:04 CST 2018]; root of context hierarchy
2018-07-29 11:08:05.517  INFO 10436 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-07-29 11:08:05.534  INFO 10436 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-29 11:08:05.568  INFO 10436 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Started DemoSpringBootScheduledApplication in 1.487 seconds (JVM running for 2.045)
Task executed at 2018-07-29T11:08:05.568
Task executed at 2018-07-29T11:08:08.598
Task executed at 2018-07-29T11:08:11.612
Task executed at 2018-07-29T11:08:14.624
...

4 fixedDelayString
fixedDelay 作用一樣,區別在於 fixedDelaylong 類型,fixedDelayStringString 類型,都是毫秒值。

5 fixedRate
以固定周期執行(單位:毫秒)

6 fixedRateString
fixedRate 作用一樣,區別在於 fixedRatelong 類型,fixedRateStringString 類型,都是毫秒值。

7 initialDelay
在第一次執行 fixedRatefixedDelay 任務之前延遲的毫秒數。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.time.LocalDateTime;

@SpringBootApplication
@EnableScheduling
public class DemoSpringBootScheduledApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoSpringBootScheduledApplication.class, args);
        // 打印應用啟動時間
        System.out.println("App start at " + LocalDateTime.now());
    }
}
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class ScheduledTask {

    @Scheduled(fixedRate = 3000, initialDelay = 5000)
    public void scheduledTask() {
        System.out.println("Task executed at " + LocalDateTime.now());
    }
}

運行定時工程,項目啟動和運行日志如下,可見應用啟動 5 秒后每 3 秒打印一次日志執行記錄。

2018-07-29 11:25:07.564  INFO 1056 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Starting DemoSpringBootScheduledApplication on LAPTOP-C375ASPB with PID 1056 (D:\JYL\DEV\IdeaProjects\demo\demo-spring-boot-scheduled\target\classes started by Ji in D:\JYL\DEV\IdeaProjects\demo)
2018-07-29 11:25:07.568  INFO 1056 --- [           main] s.b.s.DemoSpringBootScheduledApplication : No active profile set, falling back to default profiles: default
2018-07-29 11:25:07.631  INFO 1056 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@ba8d91c: startup date [Sun Jul 29 11:25:07 CST 2018]; root of context hierarchy
2018-07-29 11:25:08.736  INFO 1056 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-07-29 11:25:08.755  INFO 1056 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-07-29 11:25:08.774  INFO 1056 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Started DemoSpringBootScheduledApplication in 1.531 seconds (JVM running for 2.074)
App start at 2018-07-29T11:25:08.785
Task executed at 2018-07-29T11:25:13.789
Task executed at 2018-07-29T11:25:16.782
Task executed at 2018-07-29T11:25:19.781
...

8 initialDelayString
fixedDelay 作用一樣,區別在於 fixedDelaylong 類型,fixedDelayStringString 類型,都是毫秒值。

注意事項

1 fixedRatefixedDelay 的區別
(1) 使用 fixedRate 重寫定時任務

@Scheduled(fixedRate = 3000)
public void scheduledTask()
    throws InterruptedException {
    System.out.println("Task executed at " + LocalDateTime.now());
    Thread.sleep(10000);
}

運行日志如下:

2018-08-01 21:04:58.911  INFO 9300 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Starting DemoSpringBootScheduledApplication on LAPTOP-C375ASPB with PID 9300 (D:\JYL\DEV\IdeaProjects\demo\demo-spring-boot-scheduled\target\classes started by Ji in D:\JYL\DEV\IdeaProjects\demo)
2018-08-01 21:04:58.920  INFO 9300 --- [           main] s.b.s.DemoSpringBootScheduledApplication : No active profile set, falling back to default profiles: default
2018-08-01 21:04:59.126  INFO 9300 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4d49af10: startup date [Wed Aug 01 21:04:59 CST 2018]; root of context hierarchy
2018-08-01 21:05:00.939  INFO 9300 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-08-01 21:05:00.967  INFO 9300 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-08-01 21:05:00.990  INFO 9300 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Started DemoSpringBootScheduledApplication in 2.724 seconds (JVM running for 4.137)
Task executed at 2018-08-01T21:05:00.996
Task executed at 2018-08-01T21:05:10.997
Task executed at 2018-08-01T21:05:21.004
Task executed at 2018-08-01T21:05:31.009
......

可見,雖然定時任務設置每 3 秒一次,但是因為任務執行過程中會暫停 10 秒,所以后一次任務實際是在前一次任務結束 10 秒后執行的,盡管暫停時間間隔是任務時間間隔的 N 倍,但任務仍只會執行一次。所以定時任務的實際間隔時間變成定時設置時間間隔和任務暫停時間兩者中較大的那個。

(2) 將定時時間間隔設置為 10 秒,任務暫停時間設置為 3 秒,定時任務每 10 秒執行一次(示例代碼和運行日志略)

(3) 使用 fixedDelay 重寫定時任務,定時時間間隔設置為 3 秒,任務執行中暫停 10 秒。

@Scheduled(fixedDelay = 3000)
public void scheduledTask()
    throws InterruptedException {
    System.out.println("Task executed at " + LocalDateTime.now());
    Thread.sleep(10000);
}

運行日志如下:

2018-08-01 21:20:39.275  INFO 15468 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Starting DemoSpringBootScheduledApplication on LAPTOP-C375ASPB with PID 15468 (D:\JYL\DEV\IdeaProjects\demo\demo-spring-boot-scheduled\target\classes started by Ji in D:\JYL\DEV\IdeaProjects\demo)
2018-08-01 21:20:39.279  INFO 15468 --- [           main] s.b.s.DemoSpringBootScheduledApplication : No active profile set, falling back to default profiles: default
2018-08-01 21:20:39.340  INFO 15468 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@525b461a: startup date [Wed Aug 01 21:20:39 CST 2018]; root of context hierarchy
2018-08-01 21:20:40.498  INFO 15468 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-08-01 21:20:40.523  INFO 15468 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-08-01 21:20:40.538  INFO 15468 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Started DemoSpringBootScheduledApplication in 1.627 seconds (JVM running for 2.174)
Task executed at 2018-08-01T21:20:40.542
Task executed at 2018-08-01T21:20:53.562
Task executed at 2018-08-01T21:21:06.569
Task executed at 2018-08-01T21:21:19.578
Task executed at 2018-08-01T21:21:32.604
Task executed at 2018-08-01T21:21:45.625
Task executed at 2018-08-01T21:21:58.637
......

從日志中可以看出,除前兩次任務實際間隔時間為 7 秒(10 - 3)外,后續任務間隔時間都是 13 秒。

(4) 將定時任務時間間隔設置為 10 秒,任務執行過程中暫停 3 秒,運行日志如下:

2018-08-01 21:27:01.442  INFO 14060 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Starting DemoSpringBootScheduledApplication on LAPTOP-C375ASPB with PID 14060 (D:\JYL\DEV\IdeaProjects\demo\demo-spring-boot-scheduled\target\classes started by Ji in D:\JYL\DEV\IdeaProjects\demo)
2018-08-01 21:27:01.446  INFO 14060 --- [           main] s.b.s.DemoSpringBootScheduledApplication : No active profile set, falling back to default profiles: default
2018-08-01 21:27:01.509  INFO 14060 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@61d47554: startup date [Wed Aug 01 21:27:01 CST 2018]; root of context hierarchy
2018-08-01 21:27:02.633  INFO 14060 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-08-01 21:27:02.651  INFO 14060 --- [           main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2018-08-01 21:27:02.670  INFO 14060 --- [           main] s.b.s.DemoSpringBootScheduledApplication : Started DemoSpringBootScheduledApplication in 1.577 seconds (JVM running for 2.208)
Task executed at 2018-08-01T21:27:02.670
Task executed at 2018-08-01T21:27:15.675
Task executed at 2018-08-01T21:27:28.704
Task executed at 2018-08-01T21:27:41.713
Task executed at 2018-08-01T21:27:54.727
......

任務時間間隔變成了 13 秒(10 + 3)
從以上運行日志中可以看出 fixedRatefixedDelay 的區別。

2 cronfixedRatefixedDelay 不能共存,否則會出現以下運行期異常

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scheduledTask' defined in file [...\demo\demo-spring-boot-scheduled\target\classes\demo\spring\boot\scheduled\ScheduledTask.class]: Initialization of bean failed; nested exception is java.lang.IllegalStateException: Encountered invalid @Scheduled method 'scheduledTask': Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required
除本文中介紹的定時任務解決方案外,還有另外兩種方法實現定時任務:
(1) 使用 java.util.Timerjava.util.TimerTask 這些 Java 原生 API,優點是簡單快捷,不需要添加額外的依賴,但這些原生 API 本身也存在缺陷;
(2) 集成 QuartzElastic-Job 這些定時框架,優點是功能強大,更適合產品化應用,缺點是較重。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM