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); } }
@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()); } }
注意以上代碼使用了 @Scheduled
的 fixedRate
屬性,fixedRate
是 long
類型,表示任務執行的間隔毫秒數,以上代碼中的定時任務每 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
作用一樣,區別在於 fixedDelay
是 long
類型,fixedDelayString
是 String
類型,都是毫秒值。
5 fixedRate
以固定周期執行(單位:毫秒)
6 fixedRateString
同 fixedRate
作用一樣,區別在於 fixedRate
是 long
類型,fixedRateString
是 String
類型,都是毫秒值。
7 initialDelay
在第一次執行 fixedRate
或 fixedDelay
任務之前延遲的毫秒數。
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
作用一樣,區別在於 fixedDelay
是 long
類型,fixedDelayString
是 String
類型,都是毫秒值。
注意事項
1 fixedRate
和 fixedDelay
的區別
(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)
從以上運行日志中可以看出 fixedRate
和 fixedDelay
的區別。
2 cron
、fixedRate
和 fixedDelay
不能共存,否則會出現以下運行期異常
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.Timer
和
java.util.TimerTask
這些 Java 原生 API,優點是簡單快捷,不需要添加額外的依賴,但這些原生 API 本身也存在缺陷;
(2) 集成
Quartz
和
Elastic-Job
這些定時框架,優點是功能強大,更適合產品化應用,缺點是較重。