SpringBoot定时任务动态修改cron表达式改变执行周期

Stella981
• 阅读 1025

一、场景引入

前不久做过一个根据下载指令定时下载文件到服务器的需求。轮询下载的周期需要根据下载任务量的大小动态修改,下载任务密集的时候就周期缩小,下载任务少量时就扩大周期时间。java本身和第三方开源框架Spring共有三种执行定时任务的方式:

  1. Java自带的java.util.Timer类:这个类允许你调度一个java.util.TimerTask任务。(这种方式比较古老,自从第三方开源框架出现以来,基本不使用java自带的定时任务组件) 
  2. 开源的第三方框架: Quartz 或者 elastic-job , 但是这个比较复杂和重量级,适用于分布式场景下的定时任务,可以根据需要多实例部署定时任务。
  3. 使用Spring提供的注解: @Schedule 。 如果定时任务执行时间较短,并且比较单一,可以使用这个注解。

以上三种执行定时任务的方式都只能固定执行周期,一旦定时任务跑起来之后就不可能修改周期,只能修改周期后重新启动,现在需要动态修改执行周期,故这三种方式都不能使用。

二、解决方式

既然不能用通过传统的方式,那就要想到强大的第三方开源框架带来的便利,Spring从3.0版本开始在框架中加入了一个新的定时任务线程池配置类,即:  org.springframework.scheduling.concurrent包中有一个

ThreadPoolTaskScheduler类,它继承了抽象类 ExecutorConfigurationSupport 并实现了 AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler三个接口 ,其中实现了

TaskScheduler中的一个方法:ScheduledFuture<?> schedule(Runnable task, Trigger trigger)源码:

@Overridepublic ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {   ScheduledExecutorService executor = getScheduledExecutor();   try {      ErrorHandler errorHandler =            (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));      return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();   }   catch (RejectedExecutionException ex) {      throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);   }}通过源码即可发现只要将对应的定时任务的线程以及包含cron表达式的 Trigger 参数传入即可按指定的周期启动定时任务。通过源码也可以发现它的线程池大小默认是1:

// ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(boolean) only available on JDK 7+private static final boolean setRemoveOnCancelPolicyAvailable =      ClassUtils.hasMethod(ScheduledThreadPoolExecutor.class, "setRemoveOnCancelPolicy", boolean.class);private volatile int poolSize = 1;private volatile boolean removeOnCancelPolicy = false;private volatile ErrorHandler errorHandler;private volatile ScheduledExecutorService scheduledExecutor; 也就是说定时任务默认是单线程串行执行,如果同时需要执行多个定时任务的话,需要对线程池的大小进行配置,我配置的是线程大小是20,如下:

@Bean(autowire = Autowire.BY_NAME, value = "threadPoolConfigBean")public ThreadPoolTaskScheduler taskScheduler() {    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();    scheduler.setPoolSize(20);    return scheduler;}然后编写定时任务接口并实现:

public interface Task {    /**     * 开始任务     */    String startTask();    /**     * 停止任务     */    void stopCron();    /**     * 重新触发任务     *     * @param cron     * @return     */    boolean triggerAgain(String cron);}

实现类:

public class CacheInfoDownloadTask implements Task {    Environment env = SpringUtils.getBean(Environment.class);    // spring上下文取bean    private ThreadPoolTaskScheduler threadPoolTaskScheduler            = (ThreadPoolTaskScheduler) SpringUtils.getBean("threadPoolConfigBean");    private CacheInfoDownloadBll cacheInfoDownloadBll            = SpringUtils.getBean(CacheInfoDownloadBll.class);    private ScheduledFuture<?> future;    private static CacheInfoDownloadTask instance;    // 通过单例向外提供唯一实例    public static synchronized CacheInfoDownloadTask getInstance() {        if (null == instance) {            instance = new CacheInfoDownloadTask();        }        return instance;    }    @Override    public String startTask() {        String taskCorn = env.getProperty("cacheInfoDownload.schedule");        future = threadPoolTaskScheduler.schedule(new CacheInfoDownloadJob(), new CronTrigger(taskCorn));        log.info("缓存信息拉取开始...执行周期:{}", taskCorn);        return "ok";    }    @Override    public void stopCron() {        if (future != null) {            future.cancel(true);        }        log.info("缓存信息拉取任务停止成功...");    }    @Override    public boolean triggerAgain(String cron) {        this.stopCron();        future = this.threadPoolTaskScheduler.schedule(new CacheInfoDownloadJob(), new CronTrigger(cron));        log.info("缓存信息拉取任务更改cron表达式后再次触发成功...执行周期:{}", cron);        return true;    }    private class CacheInfoDownloadJob implements Runnable {        @Override        public void run() {            String schoolId = env.getProperty("school.id");            boolean result = cacheInfoDownloadBll.getCacheInfo(schoolId);            if (result)                log.info("缓存信息拉取并下载任务执行成功...");            else                log.error("缓存信息拉取后存储失败...");        }    }}定时任务编写成功后,使用静态工厂模式在Spring任务启动同时启动定时任务,在修改定时任务的执行周期时,只需把调用triggerAgain(String cron)方法即可修改:

@Component@Order(value = 1)public class TaskTriggerOrderConfig implements CommandLineRunner {    @Override    public void run(String... strings) throws Exception {        TaskFactory.getInstance(DOWNLOAD_TASK).startTask();    }}
点赞
收藏
评论区
推荐文章
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
@Scheduled注解
1概述@Scheduled注解是springboot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行.注意需要配合@EnableScheduling使用,配置@Scheduled主要有三种配置执行时间的方式,cron,fixedRate,fixedDelay.2croncron是
Easter79 Easter79
3年前
SpringBoot定时任务动态修改cron表达式改变执行周期
一、场景引入前不久做过一个根据下载指令定时下载文件到服务器的需求。轮询下载的周期需要根据下载任务量的大小动态修改,下载任务密集的时候就周期缩小,下载任务少量时就扩大周期时间。java本身和第三方开源框架Spring共有三种执行定时任务的方式:1)Java自带的java.util.Timer类:这个类允许你调度一个java.util.TimerT
Easter79 Easter79
3年前
Springboot自带定时任务实现动态配置Cron参数
同学们,我今天分享一下SpringBoot动态配置Cron参数。场景是这样子的:后台管理界面对定时任务进行管理,可动态修改执行时间,然后保存入库,每次任务执行前从库里查询时间,以达到动态修改Cron参数的效果。好,咱们一起来看看是怎么回事。1.Timer:这是java自带的java.util.Timer类,这个类允许你调度一个j
Stella981 Stella981
3年前
Linux的定时任务
任务计划的条件:1.在未来的某个时间点执行一次某个任务(atbatch)2.周期性的执行某个任务(cron)at在指定时间执行任务_用法_at\选项参数\\时间\_选项参数_\l      查看作业\c      显示即将执行任务的细节\d      使用任务id号
Wesley13 Wesley13
3年前
Java日期时间API系列29
  Java开发过程中经常会用到定时任务job的场景,比如定时处理数据报表等问题,开源作业调度框架也非常多,常用的开源作业调度框架有:SpringTask、Quartz和xxljob等。各个框架的具体使用不再这里讨论,这里主要讨论一下其中cron表达式的计算应用,xktime中的应用。1.SpringTask中cron表达式的计算应用
Stella981 Stella981
3年前
Spring Boot 动态修改 Scheduled (系统启动默认执行,动态修改)
场景:可配置的Scheduled执行时间,正常的Scheduled 是在项目启动的时候固定死的,没办法根据调用后台代码自动更新Scheduled执行时间例如:系统启动读取时间 Cron: 003\\?,通过执行后台方法可以动态配置 Cron时间格式,并且清楚掉原本执行任务,执行新的设置定时任务时间1、
Stella981 Stella981
3年前
SpringBoot集成Schedule任务调度
一、前言:微服务应用中可能会涉及到多个定时任务跨服务同时执行,这里就会涉及到线程生命周期和一致性问题,任务调度器本质上还是单独启动的线程执行,但是生命周期不会随应用的停止而销毁,所以本篇内容只涉及当前应用执行定时任务1、配置启动类!(https://static.oschina.net/uploads/space/2018/060
Easter79 Easter79
3年前
SpringBoot集成Schedule任务调度
一、前言:微服务应用中可能会涉及到多个定时任务跨服务同时执行,这里就会涉及到线程生命周期和一致性问题,任务调度器本质上还是单独启动的线程执行,但是生命周期不会随应用的停止而销毁,所以本篇内容只涉及当前应用执行定时任务1、配置启动类!(https://static.oschina.net/uploads/space/2018/060
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了