去皮卡多 发表于 2024-5-13 05:52:12

玩转SpringBoot:SpringBoot的几种定时任务实现方式

引言

在现代软件开辟中,定时任务是一种常见的需求,用于执行周期性的任务或在特定的时间点执行任务。这些任务可能涉及数据同步、数据备份、报表天生、缓存革新等方面,对体系的稳固性和可靠性有着重要的影响。Spring Boot提供了强大且简朴的定时任务功能,使开辟人员能够轻松地管理和执行这些任务。
本文将先容 Spring Boot中定时任务的根本用法、高级特性以及最佳实践,资助开辟人员更好地理解和应用定时任务,进步体系的稳固性和可靠性。
SpringBoot中的定时任务

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

@Scheduled注解是Spring提供的一个注解,用于标记方法作为定时任务执行。通过 @Scheduled注解,开辟人员可以轻松地设置方法在指定的时间间隔或时间点执行,实现各种定时任务需求。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {

        String cron() default "";

        long fixedDelay() default -1;

        long fixedRate() default -1;

        long initialDelay() default -1;
}以上为@Scheduled源码中关键属性,各属性寄义如下:

[*]cron: 接受标准的Unix Cron表达式,用于定义复杂的筹划执行时间。
/**
* cron属性可以设置指定时间执行,cron表达式跟linux一样
*/
@Scheduled(cron = "0 45 14 ? * *")
public void fixTimeExecution() {
        System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
}

[*]fixedRate: 以固定的频率执行任务,指定两次执行之间的间隔时间(单位是毫秒)。
/**
* fixedRate属性设置每隔固定时间执行
*/
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
        System.out.println("每隔五秒执行一次" + dateFormat.format(new Date()));
}

[*]fixedDelay:在每次任务完成后等待一定的时间再进行下一次执行,指定一连执行之间的延迟时间。
/**
* 上一次任务执行完成之后10秒后在执行
*/
@Scheduled(fixedDelay = 10000)
public void runWithFixedDelay() {
        System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
}

[*]initialDelay:首次执行前的延迟时间。
/**
* 初始延迟1秒后开始,然后每10秒执行一次
*/
@Scheduled(initialDelay=1000, fixedDelay=10000)
public void executeWithInitialAndFixedDelay() {
        System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
}这里要留意fixedRate与fixedDelay的区别:fixedRate是基于任务开始执行的时间点来盘算下一次任务开始执行的时间,因此任务的执行时间间隔是相对固定的,不受到任务执行时间的影响。假如指定的时间间隔小于任务执行的实际时间,则任务可能会并发执行。而fixedDelay是基于任务执行完成的时间点来盘算下一次任务开始执行的时间,因此任务的执行时间间隔是相对不规则的,受到任务执行时间的影响。
SpringBoot支持同时定义多个定时任务方法,每个方法可以使用不同的参数设置,以满足不同的定时任务需求。同时,我们必须在设置类中使用@EnableScheduling注解开启定时任务。
@Configuration
@EnableScheduling
public class ScheduledTaskConfig {

}大概
@EnableScheduling
@SpringBootApplication
public class SpringBootBaseApplication {

    public static void main(String[] args) {
       SpringApplication.run(SpringBootBaseApplication.class, args);
    }
}在SpringBoot应用程序中,除了在代码中使用注解设置定时任务外,还可以通过设置文件来设置定时任务的执行规则。这种方式更加灵活,可以在不修改源代码的环境下,动态调整定时任务的执行规则。好比我们在application.properties中设置@Scheduled的属性:
custom.scheduled.cron = 0/5 * * * * ?
custom.scheduled.fixedRate=5000
custom.scheduled.fixedDelay=10000
custom.scheduled.initialDelay=1000然后在@Scheduled的方法使用属性设置定时任务执行频率。
@Service
public class DemoScheduledTaskService {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    /**
   * fixedRate属性设置每隔固定时间执行
   */
    @Scheduled(fixedRateString = "${custom.scheduled.fixedRate}")
    public void reportCurrentTime() {
      System.out.println("每隔五秒执行一次" + dateFormat.format(new Date()));
    }

    /**
   * cron属性可以设置指定时间执行,cron表达式跟linux一样
   */
    @Scheduled(cron = "${custom.scheduled.cron}")
    public void fixTimeExecution() {
      System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
    }

    /**
   * 上一次任务执行完成之后10秒后在执行
   */
    @Scheduled(fixedDelayString = "${custom.scheduled.fixedDelay}")
    public void runWithFixedDelay() {
      System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
    }

    /**
   * 初始延迟1秒后开始,然后每10秒执行一次
   */
    @Scheduled(initialDelayString = "${custom.scheduled.initialDelay}", fixedDelayString = "${custom.scheduled.fixedDelay}")
    public void executeWithInitialAndFixedDelay() {
      System.out.println("指定时间 "+dateFormat.format(new Date())+"执行");
    }
}留意,这里使用属性来指定任务执行频率时,要通过@Scheduled的fixedRateString、fixedDelayString、initialDelayString三个可以指定字符串的值的属性去指定,结果等同于long类型的属性。
通过设置文件设置定时任务具有很高的灵活性,可以在不重新编译和摆设应用程序的环境下,随时调整定时任务的执行规则。同时,也可以根据不同的环境(例如开辟、测试、生产)设置不同的定时任务规则,以满足不同环境下的需求。这种方式可以有用地解耦定时任务的设置和业务代码,进步体系的灵活性和可维护性。
另外,假如渴望定时任务能够异步执行,不阻塞主线程,可以在方法上同时加上@Async注解,这样各任务就可以异步执行了。有关SpringBoot中使用@Async的解说,请移步:
固然 @Scheduled 注解是一个方便的方式来定义定时任务,但它也存在一些弊端。因为任务的执行筹划(如cron表达式)在编译时被硬编码,因此无法在运行时动态修改,除非重新摆设。此外,@Scheduled注解对于设置不同的调度策略(如使用不同的线程池)显得力不从心,而且默认环境下,@Scheduled任务在单线程环境下执行,可能出现任务堆积的环境,尤其在任务量大或任务执行时间长的环境下,而且这些任务可能会变得混乱和难以管理。定时任务的设置分散在各个任务方法中,倒霉于同一管理和维护。对于需要根据动态条件创建或烧毁定时任务的环境,@Scheduled注解也无法满足需求。
为了解决这些问题,可以使用SchedulingConfigurer接口来动态地创建和管理定时任务。通过实现 SchedulingConfigurer 接口,我们可以编写代码来动态地注册和管理定时任务,从而实现灵活的任务调度需求。接下来,我们将先容怎样使用SchedulingConfigurer接口来创建定时任务。
SchedulingConfigurer接口

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

        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
                // 定时任务逻辑
        }
}通过实现SchedulingConfigurer接口,重写configureTasks方法,自定义任务调度器的设置。此外我们还可以设置线程池,用于控制定时任务执行时的线程数量、并发性等参数。
@Bean(destroyMethod = "shutdown")
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setPoolSize(5); // 设置线程池大小
    scheduler.setThreadNamePrefix("scheduled-task-"); // 设置线程名称前缀
    scheduler.setAwaitTerminationSeconds(60); // 设置终止等待时间
        // 设置处理拒绝执行的任务异常
        scheduler.setRejectedExecutionHandler((r, executor) -> log.error("Task rejected", r));
        // 处理定时任务执行过程中抛出的未捕获异常
        scheduler.setErrorHandler(e -> log.error("Error in scheduled task", e));
    return scheduler;
}然后将自定义的ThreadPoolTaskScheduler设置到ScheduledTaskRegistrar中去:
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 定时任务逻辑
        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler());
}有关线程池的设置参数解说,请移步:
通过SchedulingConfigurer接口,可以更灵活地设置任务调度器和定时任务的执行规则,好比动态注册定时任务、动态修改任务执行规则等。

[*]动态添加定时任务
在SchedulingConfigurer的configureTasks方法中,我们可以根据业务需求,从数据库、设置文件或其它动态泉源获取定时任务的信息(如Cron表达式、任务执行类等),然后创建对应的Runnable或Callable实例,并结合Trigger(如CronTrigger)将其添加到调度器中。相比@Scheduled注解,这种方式能够在应用运行时随时添加新的定时任务。
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    ThreadPoolTaskScheduler scheduler = threadPoolTaskScheduler();
    taskRegistrar.setTaskScheduler(scheduler);

    List<CronTaskInfo> tasksFromDB = listTasksFromDatabase();

    for (CronTaskInfo task : tasksFromDB) {
      Runnable taskRunner = new MyTaskExecutor(task.getTaskData());
      CronTrigger cronTrigger = new CronTrigger(task.getCronExpression());
      scheduler.schedule(taskRunner, cronTrigger);
    }
}关于这里在应用运行时,动态的添加新的任务,我们可以通过事件驱动,轮训检查,消息队列等多种方式,监听到数据库中大概设置文件中新增任务信息,然后通过SchedulingConfigurer接口动态创建定时任务。而这种方式是@Scheduled注解做不到的。


[*]修改定时任务规则
当任务的执行规则需要动态变更时,同样可以在configureTasks方法中实现。例如,从数据库获取最新的Cron表达式,然后取消当前任务并重新添加新的任务实例。需要留意的是,取消已有任务通常需要持有对该任务的引用,例如使用Scheduler提供的unschedule方法。
// 假设我们有一个方法用于获取更新后的任务信息
CronTaskInfo updatedTask = getUpdatedTaskInfoFromDatabase();

// 取消旧的任务(需要知道旧任务的TriggerKey)
TriggerKey triggerKey = ...; // 获取旧任务的TriggerKey
scheduler.unschedule(triggerKey);

// 创建新任务并设置新的Cron表达式
MyTaskExecutor taskExecutor = new MyTaskExecutor(updatedTask.getTaskData());
CronTrigger updatedCronTrigger = new CronTrigger(updatedTask.getCronExpression());

// 重新调度新任务
scheduler.schedule(taskRunner, updatedCronTrigger);另外,我们还可以通过添加任务时对其排序或设置优先级等方式间接实现设置定时任务的执行顺序。
通过实现SchedulingConfigurer接口,我们可以拥有对定时任务调度的更多控制权,好比自定义线程池、动态添加任务以及调整任务执行策略。这种灵活性使得在复杂环境下,特殊是需要动态管理定时任务时,SchedulingConfigurer成为了抱负的选择。
其他第三方任务调度框架

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

[*]Quartz Scheduler:
Quartz是一个功能强大且灵活的任务调度库,具有丰富的功能,如支持基于cron表达式的任务调度、集群支持、作业长期化等。它可以与Spring框架集成,并且被广泛应用于各种类型的任务调度应用程序中。
[*]Elastic Job:
Elastic Job是一个分布式任务调度框架,可以轻松实现分布式任务调度和作业执行。它提供了分布式任务执行、作业依靠关系、作业分片等功能,实用于大规模的分布式任务调度场景。
[*]xxl-job:
xxl-job是一个分布式任务调度平台,提供了可视化的任务管理界面和多种任务调度方式,如单机任务、分布式任务、定时任务等。它支持任务执行日志、任务失败重试、动态调整任务执行策略等功能。
[*]PowerJob:
PowerJob是一个开源的分布式任务调度框架,由阿里巴巴团体开辟并开源。PowerJob 提供了分布式、高可用的任务调度本领,支持多种任务类型,如定时任务、延时任务、流程任务等。
总结

定时任务在现代软件开辟中扮演着重要的角色,它们可以自动化执行各种重复性的任务,进步体系的服从和可靠性。SpringBoot提供了强大而灵活的定时任务功能,使我们能够轻松地管理和执行各种定时任务。通过@Scheduled注解和SchedulingConfigurer接口,我们可以根据需求设置定时任务的执行规则,实现各种复杂的定时任务调度需求。我们可以充实利用SpringBoot中的定时任务功能,进步体系的稳固性和可靠性,从而更好地满足业务需求。
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构筹划、面试题、程序员攻略等

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 玩转SpringBoot:SpringBoot的几种定时任务实现方式