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:JYLDEVIdeaProjectsdemodemo-spring-boot-scheduled	argetclasses started by Ji in D:JYLDEVIdeaProjectsdemo)
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:JYLDEVIdeaProjectsdemodemo-spring-boot-scheduled	argetclasses started by Ji in D:JYLDEVIdeaProjectsdemo)
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:JYLDEVIdeaProjectsdemodemo-spring-boot-scheduled	argetclasses started by Ji in D:JYLDEVIdeaProjectsdemo)
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:JYLDEVIdeaProjectsdemodemo-spring-boot-scheduled	argetclasses started by Ji in D:JYLDEVIdeaProjectsdemo)
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:JYLDEVIdeaProjectsdemodemo-spring-boot-scheduled	argetclasses started by Ji in D:JYLDEVIdeaProjectsdemo)
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:JYLDEVIdeaProjectsdemodemo-spring-boot-scheduled	argetclasses started by Ji in D:JYLDEVIdeaProjectsdemo)
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 [...demodemo-spring-boot-scheduled	argetclassesdemospringootscheduledScheduledTask.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 这些定时框架,优点是功能强大,更适合产品化应用,缺点是较重。
原文地址:https://www.cnblogs.com/zouhong/p/14219989.html