引言

在现代软件开发中,定时任务是一种常见的需求,用于执行周期性的任务或在特定的时间点执行任务。这些任务可能涉及数据同步、数据备份、报表生成、缓存刷新等方面,对系统的稳定性和可靠性有着重要的影响。Spring Boot提供了强大且简单的定时任务功能,使开发人员能够轻松地管理和执行这些任务。

本文将介绍 Spring Boot中定时任务的基本用法、高级特性以及最佳实践,帮助开发人员更好地理解和应用定时任务,提高系统的稳定性和可靠性。

SpringBoot中的定时任务

SpringBoot中的定时任务主要通过@Scheduled注解以及SchedulingConfigurer接口实现。

@Scheduled注解

@Scheduled注解是Spring提供的一个注解,用于标记方法作为定时任务执行。通过 @Scheduled注解,开发人员可以轻松地配置方法在指定的时间间隔或时间点执行,实现各种定时任务需求。

  1. @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Repeatable(Schedules.class)
  5. public @interface Scheduled {
  6. String cron() default "";
  7. long fixedDelay() default -1;
  8. long fixedRate() default -1;
  9. long initialDelay() default -1;
  10. }

以上为@Scheduled源码中关键属性,各属性含义如下:

  • cron: 接受标准的Unix Cron表达式,用于定义复杂的计划执行时间。
  1. /**
  2. * cron属性可以设置指定时间执行,cron表达式跟linux一样
  3. */
  4. @Scheduled(cron = "0 45 14 ? * *")
  5. public void fixTimeExecution() {
  6. System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
  7. }
  • fixedRate: 以固定的频率执行任务,指定两次执行之间的间隔时间(单位是毫秒)。
  1. /**
  2. * fixedRate属性设置每隔固定时间执行
  3. */
  4. @Scheduled(fixedRate = 5000)
  5. public void reportCurrentTime() {
  6. System.out.println("每隔五秒执行一次" + dateFormat.format(new Date()));
  7. }
  • fixedDelay:在每次任务完成后等待一定的时间再进行下一次执行,指定连续执行之间的延迟时间。
  1. /**
  2. * 上一次任务执行完成之后10秒后在执行
  3. */
  4. @Scheduled(fixedDelay = 10000)
  5. public void runWithFixedDelay() {
  6. System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
  7. }
  • initialDelay:首次执行前的延迟时间。
  1. /**
  2. * 初始延迟1秒后开始,然后每10秒执行一次
  3. */
  4. @Scheduled(initialDelay=1000, fixedDelay=10000)
  5. public void executeWithInitialAndFixedDelay() {
  6. System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
  7. }

这里要注意fixedRate与fixedDelay的区别:fixedRate是基于任务开始执行的时间点来计算下一次任务开始执行的时间,因此任务的执行时间间隔是相对固定的,不受到任务执行时间的影响。如果指定的时间间隔小于任务执行的实际时间,则任务可能会并发执行。而fixedDelay是基于任务执行完成的时间点来计算下一次任务开始执行的时间,因此任务的执行时间间隔是相对不规则的,受到任务执行时间的影响。

SpringBoot支持同时定义多个定时任务方法,每个方法可以使用不同的参数配置,以满足不同的定时任务需求。同时,我们必须在配置类中使用@EnableScheduling注解开启定时任务。

  1. @Configuration
  2. @EnableScheduling
  3. public class ScheduledTaskConfig {
  4. }

或者

  1. @EnableScheduling
  2. @SpringBootApplication
  3. public class SpringBootBaseApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(SpringBootBaseApplication.class, args);
  6. }
  7. }

在SpringBoot应用程序中,除了在代码中使用注解配置定时任务外,还可以通过配置文件来配置定时任务的执行规则。这种方式更加灵活,可以在不修改源代码的情况下,动态调整定时任务的执行规则。比如我们在application.properties中配置@Scheduled的属性:

  1. custom.scheduled.cron = 0/5 * * * * ?
  2. custom.scheduled.fixedRate=5000
  3. custom.scheduled.fixedDelay=10000
  4. custom.scheduled.initialDelay=1000

然后在@Scheduled的方法使用属性配置定时任务执行频率。

  1. @Service
  2. public class DemoScheduledTaskService {
  3. private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
  4. /**
  5. * fixedRate属性设置每隔固定时间执行
  6. */
  7. @Scheduled(fixedRateString = "${custom.scheduled.fixedRate}")
  8. public void reportCurrentTime() {
  9. System.out.println("每隔五秒执行一次" + dateFormat.format(new Date()));
  10. }
  11. /**
  12. * cron属性可以设置指定时间执行,cron表达式跟linux一样
  13. */
  14. @Scheduled(cron = "${custom.scheduled.cron}")
  15. public void fixTimeExecution() {
  16. System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
  17. }
  18. /**
  19. * 上一次任务执行完成之后10秒后在执行
  20. */
  21. @Scheduled(fixedDelayString = "${custom.scheduled.fixedDelay}")
  22. public void runWithFixedDelay() {
  23. System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
  24. }
  25. /**
  26. * 初始延迟1秒后开始,然后每10秒执行一次
  27. */
  28. @Scheduled(initialDelayString = "${custom.scheduled.initialDelay}", fixedDelayString = "${custom.scheduled.fixedDelay}")
  29. public void executeWithInitialAndFixedDelay() {
  30. System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
  31. }
  32. }

注意,这里使用属性来指定任务执行频率时,要通过@Scheduled的fixedRateString、fixedDelayString、initialDelayString三个可以指定字符串的值的属性去指定,效果等同于long类型的属性。

通过配置文件配置定时任务具有很高的灵活性,可以在不重新编译和部署应用程序的情况下,随时调整定时任务的执行规则。同时,也可以根据不同的环境(例如开发、测试、生产)配置不同的定时任务规则,以满足不同环境下的需求。这种方式可以有效地解耦定时任务的配置和业务代码,提高系统的灵活性和可维护性。

另外,如果希望定时任务能够异步执行,不阻塞主线程,可以在方法上同时加上@Async注解,这样各任务就可以异步执行了。有关SpringBoot中使用@Async的讲解,请移步:

虽然 @Scheduled 注解是一个方便的方式来定义定时任务,但它也存在一些弊端。因为任务的执行计划(如cron表达式)在编译时被硬编码,因此无法在运行时动态修改,除非重新部署。此外,@Scheduled注解对于配置不同的调度策略(如使用不同的线程池)显得力不从心,而且默认情况下,@Scheduled任务在单线程环境下执行,可能出现任务堆积的情况,尤其在任务量大或任务执行时间长的情况下,而且这些任务可能会变得混乱和难以管理。定时任务的配置分散在各个任务方法中,不利于统一管理和维护。对于需要根据动态条件创建或销毁定时任务的情况,@Scheduled注解也无法满足需求。

为了解决这些问题,可以使用SchedulingConfigurer接口来动态地创建和管理定时任务。通过实现 SchedulingConfigurer 接口,我们可以编写代码来动态地注册和管理定时任务,从而实现灵活的任务调度需求。接下来,我们将介绍如何使用SchedulingConfigurer接口来创建定时任务。

SchedulingConfigurer接口

SchedulingConfigurer 接口是 Spring 提供的一个用于定时任务配置的扩展接口,它允许开发人员更细粒度地控制定时任务的执行。通过实现SchedulingConfigurer接口,可以自定义任务调度器(TaskScheduler),配置线程池等参数,以满足不同场景下的定时任务需求。

  1. @Configuration
  2. @EnableScheduling
  3. public class CustomSchedulingConfig implements SchedulingConfigurer {
  4. @Override
  5. public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
  6. // 定时任务逻辑
  7. }
  8. }

通过实现SchedulingConfigurer接口,重写configureTasks方法,自定义任务调度器的配置。此外我们还可以配置线程池,用于控制定时任务执行时的线程数量、并发性等参数。

  1. @Bean(destroyMethod = "shutdown")
  2. public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
  3. ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
  4. scheduler.setPoolSize(5); // 设置线程池大小
  5. scheduler.setThreadNamePrefix("scheduled-task-"); // 设置线程名称前缀
  6. scheduler.setAwaitTerminationSeconds(60); // 设置终止等待时间
  7. // 设置处理拒绝执行的任务异常
  8. scheduler.setRejectedExecutionHandler((r, executor) -> log.error("Task rejected", r));
  9. // 处理定时任务执行过程中抛出的未捕获异常
  10. scheduler.setErrorHandler(e -> log.error("Error in scheduled task", e));
  11. return scheduler;
  12. }

然后将自定义的ThreadPoolTaskScheduler设置到ScheduledTaskRegistrar中去:

  1. @Override
  2. public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
  3. // 定时任务逻辑
  4. taskRegistrar.setTaskScheduler(threadPoolTaskScheduler());
  5. }

有关线程池的配置参数讲解,请移步:

通过SchedulingConfigurer接口,可以更灵活地配置任务调度器和定时任务的执行规则,比如动态注册定时任务、动态修改任务执行规则等。

  • 动态添加定时任务

    SchedulingConfigurerconfigureTasks方法中,我们可以根据业务需求,从数据库、配置文件或其它动态来源获取定时任务的信息(如Cron表达式、任务执行类等),然后创建对应的RunnableCallable实例,并结合Trigger(如CronTrigger)将其添加到调度器中。相比@Scheduled注解,这种方式能够在应用运行时随时添加新的定时任务。
  1. @Override
  2. public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
  3. ThreadPoolTaskScheduler scheduler = threadPoolTaskScheduler();
  4. taskRegistrar.setTaskScheduler(scheduler);
  5. List<CronTaskInfo> tasksFromDB = listTasksFromDatabase();
  6. for (CronTaskInfo task : tasksFromDB) {
  7. Runnable taskRunner = new MyTaskExecutor(task.getTaskData());
  8. CronTrigger cronTrigger = new CronTrigger(task.getCronExpression());
  9. scheduler.schedule(taskRunner, cronTrigger);
  10. }
  11. }

关于这里在应用运行时,动态的添加新的任务,我们可以通过事件驱动,轮训检查,消息队列等多种方式,监听到数据库中或者配置文件中新增任务信息,然后通过SchedulingConfigurer接口动态创建定时任务。而这种方式是@Scheduled注解做不到的。

  • 修改定时任务规则

    当任务的执行规则需要动态变更时,同样可以在configureTasks方法中实现。例如,从数据库获取最新的Cron表达式,然后取消当前任务并重新添加新的任务实例。需要注意的是,取消已有任务通常需要持有对该任务的引用,例如使用Scheduler提供的unschedule方法。
  1. // 假设我们有一个方法用于获取更新后的任务信息
  2. CronTaskInfo updatedTask = getUpdatedTaskInfoFromDatabase();
  3. // 取消旧的任务(需要知道旧任务的TriggerKey)
  4. TriggerKey triggerKey = ...; // 获取旧任务的TriggerKey
  5. scheduler.unschedule(triggerKey);
  6. // 创建新任务并设置新的Cron表达式
  7. MyTaskExecutor taskExecutor = new MyTaskExecutor(updatedTask.getTaskData());
  8. CronTrigger updatedCronTrigger = new CronTrigger(updatedTask.getCronExpression());
  9. // 重新调度新任务
  10. scheduler.schedule(taskRunner, updatedCronTrigger);

另外,我们还可以通过添加任务时对其排序或设置优先级等方式间接实现设置定时任务的执行顺序。

通过实现SchedulingConfigurer接口,我们可以拥有对定时任务调度的更多控制权,比如自定义线程池、动态添加任务以及调整任务执行策略。这种灵活性使得在复杂环境下,特别是需要动态管理定时任务时,SchedulingConfigurer成为了理想的选择。

其他第三方任务调度框架

除了使用Spring框架提供的 @Scheduled 注解和SchedulingConfigurer接口外,还有许多第三方的任务调度库可供选择。这些库通常提供了更多的功能和灵活性,以满足各种复杂的任务调度需求。以下是一些常见的第三方任务调度库:

  1. Quartz Scheduler

    Quartz是一个功能强大且灵活的任务调度库,具有丰富的功能,如支持基于cron表达式的任务调度、集群支持、作业持久化等。它可以与Spring框架集成,并且被广泛应用于各种类型的任务调度应用程序中。

  2. Elastic Job

    Elastic Job是一个分布式任务调度框架,可以轻松实现分布式任务调度和作业执行。它提供了分布式任务执行、作业依赖关系、作业分片等功能,适用于大规模的分布式任务调度场景。

  3. xxl-job

    xxl-job是一个分布式任务调度平台,提供了可视化的任务管理界面和多种任务调度方式,如单机任务、分布式任务、定时任务等。它支持任务执行日志、任务失败重试、动态调整任务执行策略等功能。

  4. PowerJob

    PowerJob是一个开源的分布式任务调度框架,由阿里巴巴集团开发并开源。PowerJob 提供了分布式、高可用的任务调度能力,支持多种任务类型,如定时任务、延时任务、流程任务等。

总结

定时任务在现代软件开发中扮演着重要的角色,它们可以自动化执行各种重复性的任务,提高系统的效率和可靠性。SpringBoot提供了强大而灵活的定时任务功能,使我们能够轻松地管理和执行各种定时任务。通过@Scheduled注解和SchedulingConfigurer接口,我们可以根据需求配置定时任务的执行规则,实现各种复杂的定时任务调度需求。我们可以充分利用SpringBoot中的定时任务功能,提高系统的稳定性和可靠性,从而更好地满足业务需求。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

玩转SpringBoot:SpringBoot的几种定时任务实现方式的更多相关文章

  1. SpringBoot入门-2(两种热部署方式)

    在编写代码的时候,你会发现我们只是简单把打印信息改变了,就需要重新部署,如果是这样的编码方式,那么我们估计一天下来就真的是打几个Hello World就下班了.那么如何解决热部署的问题呢?那就是spr ...

  2. SpringBoot几种定时任务的实现方式

    定时任务实现的几种方式: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程序按照某一个频度执行, ...

  3. SpringBoot的四种定时任务

    定时任务实现的几种方式: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务. 使用这种方式可以让你的程序按照某一个频度执行 ...

  4. 【SpringBoot】几种定时任务的实现方式

    SpringBoot 几种定时任务的实现方式 Wan QingHua 架构之路  定时任务实现的几种方式: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java ...

  5. SpringBoot 定时任务实现方式

    定时任务实现的几种方式: Timer:是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程序按照某一个频度执行,但 ...

  6. SpringBoot第十七篇:定时任务

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11076555.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   相信大家对 ...

  7. springboot 基于@Scheduled注解 实现定时任务

    前言 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉, ...

  8. springboot实现定时任务的方式

    springboot实现定时任务的方式 a   Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程 ...

  9. springboot项目 @Scheduled注解 实现定时任务

    使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实 ...

  10. 补习系列(3)-springboot中的几种scope

    目标 了解HTTP 请求/响应头及常见的属性: 了解如何使用SpringBoot处理头信息 : 了解如何使用SpringBoot处理Cookie : 学会如何对 Session 进行读写: 了解如何在 ...

随机推荐

  1. 程序员必备!10款实用便捷的Git可视化管理工具

    前言 俗话说得好"工欲善其事,必先利其器",合理的选择和使用可视化的管理工具可以降低技术入门和使用的门槛.我们在团队开发中统一某个开发工具的使用能够大大降低沟通成本,提高协作沟通效 ...

  2. Unity SetActive Event

    网上查了一下Unity的SetActive变化事件没有找到,我想到用另一种思路来实现这个事件通知,它可用来调试是何处把某个gameobject隐藏掉了 Unity提供了这两个函数,OnEnable,O ...

  3. 【JVM】JDK7后intern方法总结

    JDK6及之前字符串常量池是放在永久代的,这里不讨论,JDK7之后将字符串常量池迁移到了JVM的堆中,注意删除永久代更换为元空间是JDK8哈. 测试代码1如下: @Test public void t ...

  4. Python 使用Scapy构造特殊数据包

    Scapy是一款Python库,可用于构建.发送.接收和解析网络数据包.除了实现端口扫描外,它还可以用于实现各种网络安全工具,例如SynFlood攻击,Sockstress攻击,DNS查询攻击,ARP ...

  5. ubuntu16.04编译安装nginx1.24.0

    环境: Distributor ID: Ubuntu Description: Ubuntu 16.04.7 LTS Release: 16.04 Codename: xenial 安装包: pcre ...

  6. Python - 将RTF文件转为Word 、PDF、HTML格式

    RTF也称富文本格式,是一种具有良好兼容性的文档格式,可以在不同的操作系统和应用程序之间进行交换和共享.有时出于不同项目的需求,我们可能需要将RTF文件转为其他格式.本文将介如何通过简单的Python ...

  7. Python 潮流周刊第 38 期(摘要)+赠书5本

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  8. Hadoop组件兼容性

    (1)HBase和Hadoop.zookeeper.JDK兼容版本 参考网址: https://hbase.apache.org/book.html 1)JDK和Hbase的兼容版本  对于JDK,最 ...

  9. Spring Boot + MyBatis-Plus 实现 MySQL 主从复制动态数据源切换

    MySQL 主从复制是一种常见的数据库架构,它可以提高数据库的性能和可用性.动态数据源切换则可以根据业务需求,在不同场景下使用不同的数据源,比如在读多写少的场景下,可以通过切换到从库来分担主库的压力. ...

  10. JS leetcode 反转字符串中的单词 III 题解分析

    壹 ❀ 引 又到了快乐的leetcode算法时间,今天的题目特别特别简单,来自leetcode557. 反转字符串中的单词 III,题目描述如下: 给定一个字符串,你需要反转字符串中每个单词的字符顺序 ...