阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

使用Scheduler

23次阅读
没有评论

共计 3029 个字符,预计需要花费 8 分钟才能阅读完成。

在很多应用程序中,经常需要执行定时任务。例如,每天或每月给用户发送账户汇总报表,定期检查并发送系统状态报告,等等。

定时任务我们在使用线程池一节中已经讲到了,Java 标准库本身就提供了定时执行任务的功能。在 Spring 中,使用定时任务更简单,不需要手写线程池相关代码,只需要两个注解即可。

我们还是以实际代码为例,建立工程 spring-integration-schedule,无需额外的依赖,我们可以直接在AppConfig 中加上 @EnableScheduling 就开启了定时任务的支持:

@Configuration
@ComponentScan
@EnableWebMvc
@EnableScheduling
@EnableTransactionManagement
@PropertySource({"classpath:/jdbc.properties", "classpath:/task.properties"})
public class AppConfig {...}

接下来,我们可以直接在一个 Bean 中编写一个 public void 无参数方法,然后加上 @Scheduled 注解:

@Component
public class TaskService {final Logger logger = LoggerFactory.getLogger(getClass());

    @Scheduled(initialDelay = 60_000, fixedRate = 60_000)
    public void checkSystemStatusEveryMinute() {logger.info("Start check system status...");
    }
}

上述注解指定了启动延迟 60 秒,并以 60 秒的间隔执行任务。现在,我们直接运行应用程序,就可以在控制台看到定时任务打印的日志:

2020-06-03 18:47:32 INFO  [pool-1-thread-1] c.i.learnjava.service.TaskService - Start check system status...
2020-06-03 18:48:32 INFO  [pool-1-thread-1] c.i.learnjava.service.TaskService - Start check system status...
2020-06-03 18:49:32 INFO  [pool-1-thread-1] c.i.learnjava.service.TaskService - Start check system status...

如果没有看到定时任务的日志,需要检查:

  • 是否忘记了在 AppConfig 中标注@EnableScheduling
  • 是否忘记了在定时任务的方法所在的 class 标注@Component

除了可以使用 fixedRate 外,还可以使用fixedDelay,两者的区别我们已经在使用线程池一节中讲过,这里不再重复。

有的童鞋在实际开发中会遇到一个问题,因为 Java 的注解全部是常量,写死了fixedDelay=30000,如果根据实际情况要改成 60 秒怎么办,只能重新编译?

我们可以把定时任务的配置放到配置文件中,例如task.properties

task.checkDiskSpace=30000

这样就可以随时修改配置文件而无需动代码。但是在代码中,我们需要用 fixedDelayString 取代fixedDelay

@Component
public class TaskService {
    ...

    @Scheduled(initialDelay = 30_000, fixedDelayString = "${task.checkDiskSpace:30000}")
    public void checkDiskSpaceEveryMinute() {logger.info("Start check disk space...");
    }
}

注意到上述代码的注解参数 fixedDelayString 是一个属性占位符,并配有默认值 30000,Spring 在处理 @Scheduled 注解时,如果遇到String,会根据占位符自动用配置项替换,这样就可以灵活地修改定时任务的配置。

此外,fixedDelayString还可以使用更易读的Duration,例如:

@Scheduled(initialDelay = 30_000, fixedDelayString = "${task.checkDiskSpace:PT2M30S}")

以字符串 PT2M30S 表示的 Duration 就是 2 分 30 秒,请参考 LocalDateTime 一节的 Duration 相关部分。

多个 @Scheduled 方法完全可以放到一个 Bean 中,这样便于统一管理各类定时任务。

使用 Cron 任务

还有一类定时任务,它不是简单的重复执行,而是按时间触发,我们把这类任务称为 Cron 任务,例如:

  • 每天凌晨 2:15 执行报表任务;
  • 每个工作日 12:00 执行特定任务;
  • ……

Cron 源自 Unix/Linux 系统自带的 crond 守护进程,以一个简洁的表达式定义任务触发时间。在 Spring 中,也可以使用 Cron 表达式来执行 Cron 任务,在 Spring 中,它的格式是:

秒 分 小时 天 月份 星期 年

年是可以忽略的,通常不写。每天凌晨 2:15 执行的 Cron 表达式就是:

0 15 2 * * *

每个工作日 12:00 执行的 Cron 表达式就是:

0 0 12 * * MON-FRI

每个月 1 号,2 号,3 号和 10 号 12:00 执行的 Cron 表达式就是:

0 0 12 1-3,10 * *

在 Spring 中,我们定义一个每天凌晨 2:15 执行的任务:

@Component
public class TaskService {
    ...

    @Scheduled(cron = "${task.report:0 15 2 * * *}")
    public void cronDailyReport() {logger.info("Start daily report task...");
    }
}

Cron 任务同样可以使用属性占位符,这样修改起来更加方便。

Cron 表达式还可以表达每 10 分钟执行,例如:

0 */10 * * * *

这样,在每个小时的 0:00,10:00,20:00,30:00,40:00,50:00 均会执行任务,实际上它可以取代 fixedRate 类型的定时任务。

集成 Quartz

在 Spring 中使用定时任务和 Cron 任务都十分简单,但是要注意到,这些任务的调度都是在每个 JVM 进程中的。如果在本机启动两个进程,或者在多台机器上启动应用,这些进程的定时任务和 Cron 任务都是独立运行的,互不影响。

如果一些定时任务要以集群的方式运行,例如每天 23:00 执行检查任务,只需要集群中的一台运行即可,这个时候,可以考虑使用 Quartz。

Quartz 可以配置一个 JDBC 数据源,以便存储所有的任务调度计划以及任务执行状态。也可以使用内存来调度任务,但这样配置就和使用 Spring 的调度没啥区别了,额外集成 Quartz 的意义就不大。

Quartz 的 JDBC 配置比较复杂,Spring 对其也有一定的支持。要详细了解 Quartz 的集成,请参考 Spring 的文档。

思考:如果不使用 Quartz 的 JDBC 配置,多个 Spring 应用同时运行时,如何保证某个任务只在某一台机器执行?

练习

使用 Scheduler 执行定时任务。

下载练习

小结

Spring 内置定时任务和 Cron 任务的支持,编写调度任务十分方便。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计3029字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中