基于SchedulingConfigurer实现多定时任务注册及动态修改执行周期
Java中实现定时任务有三种方式:
1.JDK自带的Timer
2.Spring中的Spring Task
3.借助第三方的Quartz
工作中常用的是Spring Task和Quartz,今天主要用一下Spring Task。
Spring Task有两种实现方式:
1.@Scheduled(cron表达式)
2.基于SchedulingConfigurer注册定时任务
这两者的区别主要有
1.@Scheduled不支持动态修改定时周期,只能停止服务器,修改cron表达式,再启动服务器;SchedulingConfigurer可以动态修改
2.@Scheduled只能是单线程,而SchedulingConfigurer默认是单线程,可以通过添加线程池,实现多线程下定时任务的运行
接下来我们一步步操作下基于SchedulingConfigurer的定时任务实现
一、在Spring Boot中实现一个简单的定时任务,熟悉ScheduledTaskRegistrar的用法

/**
* 先注册,后执行(根据返回的时间执行)
**/
@EnableScheduling
@Slf4j
@Configuration
public class TaskConfig implements SchedulingConfigurer {
    private String cron = "0 0/5 * * * ?";
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        log.info("***************初始化定时任务设置开始*************");
        //lambda表达式实现简单的任务逻辑
            scheduledTaskRegistrar.addTriggerTask(() -> {
            log.info("执行了定时任务");
        },sheduledConfig->{
                //设置定时任务的执行频率并返回下次执行时间
                Date date = new CronTrigger(cron).nextExecutionTime(sheduledConfig);
                log.info("执行定时任务的时间:" + formatDate(date));
                return date;
            });
        }
    }
}

注意几个地方:
1.添加@EnableScheduling注解,使得当前类支持定时任务
2.实现SchedulingConfigurer接口,重写configureTasks方法
我们看下addTriggerTask方法及其两个参数:

 public void addTriggerTask(Runnable task, Trigger trigger) {
        this.addTriggerTask(new TriggerTask(task, trigger));
    }
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable是一个函数式接口,用来定义定时任务的执行逻辑,内部的run方法会被定时任务触发器回调执行,单个简单的任务逻辑我们可以采用上面的Lambda表达式实现,多个定时任务且逻辑复杂的话,采用另外的方式实现,下面会讲。

public interface Trigger {
    @Nullable
    Date nextExecutionTime(TriggerContext var1);
}

Trigger:定时任务的周期设置,该接口的实现类有CronTrigger和PeriodicTrigger,一般使用CronTrigger来设置定时任务的执行频率,接口会返回下次定时任务的执行时间。
二、实现多任务,不同频率的定时任务注册和执行

addTriggerTask(Runnable task, Trigger trigger)

分析这个方法,实现上面功能的大致逻辑如下:
1.因为Runnable:任务执行逻辑,只能传入一个任务,所以如果是多个任务的话,可以采用循环一个个注册,那既然要循环的话,我们就需要获取所有的定时任务类,那如何获取所有的定时任务类呢,还是通过入参参数思考,定时任务类需要实现Runable接口,那么我们就可以通过Bean的类型来获取所有实现Runable接口的类,遍历执行就可以了
2.定时任务执行周期:跟着1思考,每个定时任务的执行频率不同,我们就把这个参数放到定时任务类中作为属性,循环注册时,可以拿到定时任务类,也就可以通过类拿到定时任务的执行频率了
按照上面的思路,开始撸代码
1.定义接口

public interface BaskTask extends Runnable {
     //获取执行频率
    String getCron();
    //执行任务逻辑
    void execute();
}

2.定义两个定时任务类

@Slf4j
@Component
@Data
public class TaskOne implements BaskTask {

    //从配置文件获取任务执行频率
    @Value("${task.taskOneCron}")
    private String taskCron;

   //外部获取执行频率
    @Override
    public String getCron() {
        return taskCron;
    }
    
    @Override
    public void execute() {
         //可写复杂的任务逻辑
        log.info("taskOne在"+ formatDate(new Date()) +"执行了")
    }
    //执行任务
    @Override
    public void run() {
        execute();
    }
}

这里说一下任务执行频率,其实将执行频率写在配置文件中不能体现出可以动态修改执行频率的效果,因为配置文件修改就得重启,最理想的做法就是通过页面输入执行频率,同步到这里(可以实现)

@Slf4j
@Component
@Data
public class TaskTwo implements BaskTask {

    @Value("${task.taskTwoCron}")
    private String taskCron;

    @Override
    public String getCron() {
        return taskCron;
    }
    
    @Override
    public void execute() {
        log.info("taskTwo在"+ formatDate(new Date()) +"执行了");
    }
    
    @Override
    public void run() {
       execute();
    }
}

3.定义定时任务注册器

@EnableScheduling
@Slf4j
@Configuration
public class TaskConfig implements SchedulingConfigurer {
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
         //获取所有的定时任务
        Map<String,BaskTask> map = applicationContext.getBeansOfType(BaskTask.class);

        //遍历注册
        for(String key :map.keySet()){
            BaskTask baskTask = map.get(key);
            //baskTask:回调内部的run方法
            //baskTask.getCron() 获取各个任务的执行频率
            scheduledTaskRegistrar.addTriggerTask(baskTask,sheduledConfig->{
                Date date = new CronTrigger(baskTask.getCron()).nextExecutionTime(sheduledConfig);
                log.info(key + "执行定时任务的时间:" + formatDate(date));
                return date;
            });
        }
    }
    }

4.执行结果如下:
在这里插入图片描述
5.细心的小伙伴可以发现,两个定时任务是在同一个线程中执行的,那么可不可以实现在不同的线程中执行呢,答案是肯定的,因为SchedulingConfigurer是支持多线程的,只需要向定时任务注册器中添加线程池就好了,如下:

//在注册器添加定时任务前添加线程池
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();
scheduledTaskRegistrar.setTaskScheduler(taskScheduler);

再运行结果如下:
在这里插入图片描述
最后留个悬念吧:如果某个定时任务不想让继续运行了,又该怎么做呢,有没有小伙伴注意到这行日志:No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
和这个有关系,剩下的内容下篇再写!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐