假设,我们有一个数据同步的需求:每隔5秒执行一次数据同步。那么我们该如何实现这个数据同步任务呢?
哈喽,大家好,我是小冯。
今天给分享在Spring Boot项目中使用@Scheduled实现定时任务。
我们就上面的需求,基于Spring Boot框架,搭建一个简单的数据同步调度任务。
Demo如下。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
因为我们是基于Spring Boot开发,所以不需要其他依赖。
package com.fengwenyi.demospringbootscheduled; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author <a href="https://www.fengwenyi.com">Erwin Feng</a> * @since 2021-09-29 */ @SpringBootApplication public class DemoSpringBootScheduledApplication { public static void main(String[] args) { SpringApplication.run(DemoSpringBootScheduledApplication.class, args); } }
package com.fengwenyi.demospringbootscheduled.config; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; /** * @author <a href="https://www.fengwenyi.com">Erwin Feng</a> * @since 2021-09-29 */ @Configuration @EnableScheduling public class ScheduledConfiguration { }
package com.fengwenyi.demospringbootscheduled.task; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * @author <a href="https://www.fengwenyi.com">Erwin Feng</a> * @since 2021-10-21 */ @Component @Slf4j public class DemoTask { @Scheduled(initialDelay = 5, fixedRate = 5, timeUnit = TimeUnit.SECONDS) public void dataSynchronizationTask() { log.info("开始执行数据同步任务"); } }
通过意思步骤,我们的demo就搭建好了,跑一下,控制台打印日志如下:
2021-10-21 21:44:55.711 INFO 10320 --- [ scheduling-1] c.f.d.task.DemoTask : 开始执行数据同步任务 2021-10-21 21:45:00.705 INFO 10320 --- [ scheduling-1] c.f.d.task.DemoTask : 开始执行数据同步任务 2021-10-21 21:45:05.715 INFO 10320 --- [ scheduling-1] c.f.d.task.DemoTask : 开始执行数据同步任务 2021-10-21 21:45:10.710 INFO 10320 --- [ scheduling-1] c.f.d.task.DemoTask : 开始执行数据同步任务
通过打印日志,我们指定,没间隔5秒,就会自动执行“数据同步任务”,这样就简单实现了任务调度。
下面我们对 @Scheduled
注解提供配置,做一个说明。
先看一个例子:每5秒执行一次任务。
@Scheduled(cron = "0/5 * * * * ? ") public void testCron01() { log.info("test cron 01 exec"); }
执行:
2021-10-23 02:31:50.030 INFO 18872 --- [ scheduling-1] c.f.d.task.ScheduledTask : test cron 1 exec 2021-10-23 02:31:55.009 INFO 18872 --- [ scheduling-1] c.f.d.task.ScheduledTask : test cron 1 exec 2021-10-23 02:32:00.005 INFO 18872 --- [ scheduling-1] c.f.d.task.ScheduledTask : test cron 1 exec
关于cron表达式,下面要做几点说明:
1、结构
┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (0 - 7) │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
spring支持的cron表达式,由6位构成,分别表示:
2、Cron表达式示例
通过阅读一些cron示例,更能理解cron表达式的具体含义,我们就以spring官方文档中的示例进行学习。
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-cron-expression
星号(*
)和问号(?
)都表示通配符,其中,?可以用在 天(月)
和 天(星期)
上,即第4位和第6位。
L
,表示最后,比如一月最后一个星期天。
W
,表示工作日(周一到周五)。
#
,表示每月中的第几个星期几。5#2
:表示每月第2个星期五。MON#1
:表示每月第1个星期一。
3、Macros
spring为我们提供了几个特别的cron表达式(整年,整月,整周,整天或者整夜,整小时),我们可以直接用。
@Scheduled(cron = "@hourly") public void testCron02() { log.info("test cron 02 exec"); }
时区
固定间隔,参数类型为long。
固定间隔,参数类型为String,同fixedDelay。
固定速率,参数类型为long。
固定速率,参数类型为long,同fixedRate。
时间单位,从 5.3.10开始
spring boot 2.5.5开始
第一次延时时间,参数类型为long。
第一次延时时间,参数类型为String。
fixedDelay,间隔时间,以任务结束时间算起。
fixedRate,间隔时间,以任务开始时间算起。
比如一个任务,间隔时间为5秒,任务执行时间是2秒。
假设fixedDelay在第5秒执行第一次,那么第二次会在12秒执行。
而fixedRate在第5秒执行第一次,那么第二次会在10秒执行。
比如一个任务,间隔时间为2秒,任务执行时间是5秒。
假设fixedDelay在第2秒执行第一次,那么第二次会在9秒执行。
而fixedRate在第2秒执行第一次,那么第二次会在7秒执行。
在实际项目中,执行时间一般写在配置文件中,方便修改,不然,如果要修改,还要改代码。
关于如何写在配置文件中,相信你一定遇到过这个问题。
这部分我们解决这样一个问题,并进行总结。
@Scheduled(cron = "${erwin.cron:0/2 * * * * ?}") public void cronTaskYmlDemo() { log.info("cron yml demo"); }
配置:
erwin: cron: 0/10 * * * * ?
如果配置文件没有配,就会使用默认的值。
请注意,值为空,不等于没有配。
在上面参数解释的时候,我们指定,这个接收的是一个整数,那该如何将解决这个问题。
相信聪明的你,一定也是猜到了。
对,没错,就是它。
@Scheduled(initialDelay = 5, fixedDelayString = "${erwin.fixed-delay:2}", timeUnit = TimeUnit.SECONDS) public void fixedDelayTaskYmlDemo() { log.info("fixedDelay yml demo"); }
配置:
erwin: fixed-delay: 5
简单解释一下,如果在配置文件中没有配置,则每隔2秒执行一次,如果配置了,就每隔5秒执行一次。initialDelay
表示,项目启动后,5秒开始执行第一次任务。
值得注意的是,${erwin.fixed-delay:2},冒号前后不能有空格。
有了上面的经验,相信你一定学会了。我们一起来看示例吧。
@Scheduled(initialDelay = 5, fixedRateString = "${erwin.fixed-rate:2}", timeUnit = TimeUnit.SECONDS) public void fixedRateTaskYmlDemo() { log.info("fixedRate yml demo"); }
配置:
erwin: fixed-rate: 5
执行示例:
2021-10-25 20:41:57.394 INFO 19368 --- [ scheduling-1] c.f.d.task.DemoTask : fixedRate yml demo 2021-10-25 20:41:59.394 INFO 19368 --- [ scheduling-1] c.f.d.task.DemoTask : fixedRate yml demo 2021-10-25 20:42:01.394 INFO 19368 --- [ scheduling-1] c.f.d.task.DemoTask : fixedRate yml demo
最后的最后,还有一个问题,先看图。
发现问题了吗?
我们在写配置的时候,没有提示,并且这种看上去,也不友好。
那要怎么解决呢?
先引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
我们不妨写一个属性配置类。
@Getter @Setter @Configuration @ConfigurationProperties("erwin") public class ErwinProperties { private String cron; private Long fixedDelay; private Long fixedRate; }
你注意到 erwin
这个了吗?
刚开始写示例的时候,你是不是很好奇,为什么会有这个前缀
哈哈,其实我们早已埋下了伏笔。
最后,再来看看吧。
同时,这时候,你再写配的时候,就会有提示了。
今天分享的内容,就是这些了,咱们下期再见!