Springboot实现定时任务调度

时间:2022-12-28 11:55:02

Springboot实现定时任务调度

前言

        今天给大家分享一下,如何使用springboot快速实现简单的定时调度任务?有两种方法:基于注解的声明式调度任务注册;另外一种是基于实现SchedulingConfigurer的编程式的调度任务注册。

1.  基于注解(@Scheduled)

        注解内有8个属性,分别是cron、fixedDelay、fixedDelayString、fixedRate、fixedRateString、initialDelay、initialDelayString、zone,使用方法很简单,分为两步:

  1. 使用@Configuration、@EnableScheduling,声明并开启调度任务类;
  2. 使用@Scheduled标记调度任务执行逻辑;
@Configuration
@EnableScheduling
public class StudentTask {
    @Scheduled(cron = "0/5 * * * * ?")
    private void configureTasks() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
    @Scheduled(cron = "0/10 * * * * ?")
    private void configureTasks2() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

1.1. cron表达式

        使用cron表达式来定义调度任务的执行规则,cron表达式是由若干数字、空格、符号按一定的规则,组成一组字符串,从而表达时间的信息。与正则表达式类似,都是一个字符串表示一些信息。

1.1.1 cron表达式的书写规则

        cron表达式的标准结构是一个字符串,以空格分隔为6或7个域,每个域代表一个含义,从左到右分别是:[秒] [分] [小时] [日] [月] [周]

第一个位置: 秒:区间 0-59 秒,代表一分钟内的秒数。

第二个位置:分:区间 0-59 分,代表一小时内的分钟数。

第三个位置:时:区间 0-23 时,代表一天中的小时数。

第四个位置: 日:区间 1-31 (?根据每月有多少天来),代表一月中的多少号。

第五个位置:月:区间 1-12 ,代表一年中的月份。

第六个位置:周:区间 1-7或者英文星期的缩写,代表星期几。

每个域允许使用特殊字符组成一些通配符表示不同的调度规则,可以使用的特殊字符如下:

“0-9”:数值,出现在标识符位置的数字代表对应值,比如出现在第一个位置就表示秒;

“*”:所有可能的值,如在秒域上出现,表示每一秒;在分域上出现,表示每一分; “?” :忽略,不指定具体的值,只有在日域、周域上可以使用;

“/” :间隔,指定数值的增量,语义相当于每隔… 比如例2中的第三个位置的2/5就表示从2点开始每隔五小时

“-”:区间,如分钟域中,5-20 表示从5分钟到20分钟之间每隔一分钟触发一次

“,”:列出枚举值,如在分钟域中,5,20表示分别在5分钟和20分钟触发一次

“L”:表示最后一天,仅日期和星期域支持该字符,在日期域中,L表示某个月的最后一天。在星期域中L表示一个星期的最后一天,也就是星期日(SUN)。如果在L前有具体的内容,例如,在星期域中的6L表示这个月的最后一个星期六。

“W ”:除周末以外的有效工作日,在离指定日期的最近的有效工作日触发事件。W字符寻找最近有效工作日时不会跨过当前月份,连用字符LW时表示为指定月份的最后一个工作日,在日期域中5W,如果5日是星期六,则将在最近的工作日星期五,即4日触发。如果5日是星期天,则将在最近的工作日星期一,即6日触发;如果5日在星期一到星期五中的一天,则就在5日触发。

“#”:周定位,仅周域支持该字符,语义相当于每月的第几个周几 比如例4中的第六个位置的2#3就表示第三个周一。

1.1.2 cron表达式举例

0 15 10 ? * *

每天上午10:15执行任务

0 15 10 * * ?

每天上午10:15执行任务

0 0 12 * * ?

每天中午12:00执行任务

0 0 10,14,16 * * ?

每天上午10:00点、下午14:00以及下午16:00执行任务

0 0/30 9-17 * * ?

每天上午09:00到下午17:00时间段内每隔半小时执行任务

0 * 14 * * ?

每天下午14:00到下午14:59时间段内每隔1分钟执行任务

0 0-5 14 * * ?

每天下午14:00到下午14:05时间段内每隔1分钟执行任务

0 0/5 14 * * ?

每天下午14:00到下午14:55时间段内每隔5分钟执行任务

0 0/5 14,18 * * ?

每天下午14:00到下午14:55、下午18:00到下午18:55时间段内每隔5分钟执行任务

0 0 12 ? * WED

每个星期三中午12:00执行任务

1.2 fixedDelay

        从上次调用结束到下一次调用之间的固定时间(以毫秒为单位),比如fixedDelay=5000,表示当调度任务被触发并且调度任务执行完成后,延迟5秒,调度任务再次被触发;但是需要注意的是fixedDelay、cron、fixedRate不能同时使用。

Springboot实现定时任务调度

1.3 fixedDelayString

        与fixedDelay的含义和使用基本相同,唯一区别fixedDelayString使用字符串的形式,支持占位符。

1.4fixedRate

        fixedRate 两次调用之间固定的毫秒数,比如fixedRate=5000,表示当调度任务第一次执行被触发后,后续不管上一次的任务是否完成,都会在上一次触发后5秒钟再次触发调度任务。

1.5 fixedRateString

        与 fixedRate的含义和使用基本相同,唯一区别是fixedRateString使用字符串的形式,支持占位符。

1.6 initialDelay

        第一次执行 fixedRate 或者 fixedDelay 任务之前要延迟的毫秒数,比如initialDelay=5000,表示 fixedRate 或者 fixedDelay的调度任务第一次执行时会延迟5秒,需要注意的是使用cron表达时,不能使用initialDelay属性。

Springboot实现定时任务调度

1.7 zone

        时区,默认是一个空字符串,即取服务器所在地的进区,cron表达式会基于该时区进行解析;

2. 基于实现SchedulingConfigurer接口

  1. 主要通过实现org.springframework.scheduling.annotation.SchedulingConfigurer接口,重写configureTasks()方法实现调度任务的注册;
  2. 实现java.lang.Runnable接口,在run方法中编写调度任务的执行逻辑;
  3. 使用org.springframework.scheduling.support.CronTrigger声明一个cron表达式类型的触发器
  4. 使用上两步的实例对象作为触发器任务的构造参数实例化一个org.springframework.scheduling.config.TriggerTask对象;
  5. 重写configureTasks()方法时,使用调度任务注册器scheduledTaskRegistrar(org.springframework.scheduling.config.ScheduledTaskRegistrar)注册触发器任务(TriggerTask)
@Configuration
public class DynamicScheduleTask implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        List<TriggerTask> list = new ArrayList<>();
        BeginClassTask beginClassTask = new BeginClassTask();
        TriggerTask triggerTask = new TriggerTask(beginClassTask, new CronTrigger("0/5 * * * * ?"));
        list.add(triggerTask);
        EndClassTask endClassTask = new EndClassTask();
        TriggerTask triggerTask1 = new TriggerTask(endClassTask, new CronTrigger("0/10 * * * * ?"));
        list.add(triggerTask1);
        //将任务列表注册到定时器
        scheduledTaskRegistrar.setTriggerTasksList(list);
    }
}

3. 总结

        基于注解形式的调度任务注册比较简单,但是不灵活,没有办法在程序运行状态时动态对已注册的调度任务进行添加和删除;

        基于实现基于实现SchedulingConfigurer接口的形式,因为是通过编程式实现调度任务的注册,所以可以通过这种方式实现在程序运行状态时动态对已注册的调度任务进行添加和删除,但是缺点也比较 明显,实现的过程比较繁琐,不如使用注解那般灵活。

        最后要说一点,对于一般性的调度任务执行场景,使用上面两种方法中的一种都是没有问题的。但是如果涉及大量的、并发比较高、执行精度比较高、分布式部署的场景,其实是不太适用的,可以选择其他更优的方案。