Spring Framework 自身提供了對定時(shí)任務(wù)的支持,本文介紹 Spring Boot 中 @Scheduled 定時(shí)器的使用。
首先,在項(xiàng)目啟動(dòng)類上添加 @EnableScheduling 注解,開啟對定時(shí)任務(wù)的支持
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);
}
}
其次,編寫定時(shí)任務(wù)類和方法,定時(shí)任務(wù)類通過 Spring IOC 加載,使用 @Component 注解(當(dāng)然也可以使用 @Controller 和 @Service 等其他與 @Component 作用相同的注解),定時(shí)方法使用 @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 類型,表示任務(wù)執(zhí)行的間隔毫秒數(shù),以上代碼中的定時(shí)任務(wù)每 3 秒執(zhí)行一次。
運(yùn)行定時(shí)工程,項(xiàng)目啟動(dòng)和運(yùn)行日志如下,可見每 3 秒打印一次日志執(zhí)行記錄。
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(計(jì)劃任務(wù))表達(dá)式廣泛應(yīng)用于各種定時(shí)解決方案,參考 Cron 表達(dá)式詳解
2 zone
用于解析 Cron 表達(dá)式的時(shí)區(qū)
3 fixedDelay
上次調(diào)用結(jié)束和下一次調(diào)用結(jié)束之間的固定周期(單位:毫秒),即上一次執(zhí)行完畢時(shí)間點(diǎn)之后延遲執(zhí)行。
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());
}
}
運(yùn)行定時(shí)工程,項(xiàng)目啟動(dòng)和運(yùn)行日志如下,可見每 3 秒打印一次日志執(zhí)行記錄。
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 作用一樣,區(qū)別在于 fixedDelay 是 long 類型,fixedDelayString 是 String 類型,都是毫秒值。
5 fixedRate
以固定周期執(zhí)行(單位:毫秒)
6 fixedRateString
同 fixedRate 作用一樣,區(qū)別在于 fixedRate 是 long 類型,fixedRateString 是 String 類型,都是毫秒值。
7 initialDelay
在第一次執(zhí)行 fixedRate 或 fixedDelay 任務(wù)之前延遲的毫秒數(shù)。
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);
// 打印應(yīng)用啟動(dòng)時(shí)間
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());
}
}
運(yùn)行定時(shí)工程,項(xiàng)目啟動(dòng)和運(yùn)行日志如下,可見應(yīng)用啟動(dòng) 5 秒后每 3 秒打印一次日志執(zhí)行記錄。
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 作用一樣,區(qū)別在于 fixedDelay 是 long 類型,fixedDelayString 是 String 類型,都是毫秒值。
注意事項(xiàng)
1 fixedRate 和 fixedDelay 的區(qū)別
(1) 使用 fixedRate 重寫定時(shí)任務(wù)
@Scheduled(fixedRate = 3000)
public void scheduledTask()
throws InterruptedException {
System.out.println("Task executed at " + LocalDateTime.now());
Thread.sleep(10000);
}
運(yùn)行日志如下:
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
......
可見,雖然定時(shí)任務(wù)設(shè)置每 3 秒一次,但是因?yàn)槿蝿?wù)執(zhí)行過程中會(huì)暫停 10 秒,所以后一次任務(wù)實(shí)際是在前一次任務(wù)結(jié)束 10 秒后執(zhí)行的,盡管暫停時(shí)間間隔是任務(wù)時(shí)間間隔的 N 倍,但任務(wù)仍只會(huì)執(zhí)行一次。所以定時(shí)任務(wù)的實(shí)際間隔時(shí)間變成定時(shí)設(shè)置時(shí)間間隔和任務(wù)暫停時(shí)間兩者中較大的那個(gè)。
(2) 將定時(shí)時(shí)間間隔設(shè)置為 10 秒,任務(wù)暫停時(shí)間設(shè)置為 3 秒,定時(shí)任務(wù)每 10 秒執(zhí)行一次(示例代碼和運(yùn)行日志略)
(3) 使用 fixedDelay 重寫定時(shí)任務(wù),定時(shí)時(shí)間間隔設(shè)置為 3 秒,任務(wù)執(zhí)行中暫停 10 秒。
@Scheduled(fixedDelay = 3000)
public void scheduledTask()
throws InterruptedException {
System.out.println("Task executed at " + LocalDateTime.now());
Thread.sleep(10000);
}
運(yùn)行日志如下:
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
......
從日志中可以看出,除前兩次任務(wù)實(shí)際間隔時(shí)間為 7 秒(10 - 3)外,后續(xù)任務(wù)間隔時(shí)間都是 3 秒。
(4) 將定時(shí)任務(wù)時(shí)間間隔設(shè)置為 10 秒,任務(wù)執(zhí)行過程中暫停 3 秒,運(yùn)行日志如下:
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
......
任務(wù)時(shí)間間隔變成了 13 秒(10 + 3)
從以上運(yùn)行日志中可以看出 fixedRate 和 fixedDelay 的區(qū)別。
2 cron、fixedRate 和 fixedDelay 不能共存,否則會(huì)出現(xiàn)以下運(yùn)行期異常
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
除本文中介紹的定時(shí)任務(wù)解決方案外,還有另外兩種方法實(shí)現(xiàn)定時(shí)任務(wù):
(1) 使用 java.util.Timer 和 java.util.TimerTask 這些 Java 原生 API,優(yōu)點(diǎn)是簡單快捷,不需要添加額外的依賴,但這些原生 API 本身也存在缺陷;
(2) 集成 Quartz 和 Elastic-Job 這些定時(shí)框架,優(yōu)點(diǎn)是功能強(qiáng)大,更適合產(chǎn)品化應(yīng)用,缺點(diǎn)是較重。