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());
}
}
注意以上代码使用了 @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)外,后续任务间隔时间都是 3 秒。
(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
这些定时框架,优点是功能强大,更适合产品化应用,缺点是较重。