SpringBoot 动态定时任务

SpringBoot 动态定时任务

最近在工作中遇到了springBoot 动态执行定时任务的场景,通过数据库动态指定任务的执行频率,禁用或启动定时任务等,通过寻找资料,成功解决了这一场景, 一起分享一下吧

1 数据库设计:

CREATE TABLE `spring_scheduled_cron` (
  `cron_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `cron_key` varchar(128) NOT NULL COMMENT '定时任务完整类名(该类必须继承 TimingTask类)',
  `cron_expression` varchar(20) NOT NULL COMMENT 'cron表达式',
  `task_explain` varchar(50) NOT NULL DEFAULT '' COMMENT '任务描述',
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:正常;2:停用',
  PRIMARY KEY (`cron_id`),
  UNIQUE KEY `cron_key` (`cron_key`),
  UNIQUE KEY `cron_key_unique_idx` (`cron_key`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务表';

2 编写查询数据库的Mapper

public interface ScheduleMapper {
    
    
    /**
     * 获取有效得定时任务
     * */
    @Select(" select cron_id,cron_key,cron_expression,task_explain,status 
              from spring_scheduled_cron ")
    public List<Schedule> getAll();
}

3 自定义任务类

public abstract class TimingTask implements Runnable {
    
    
   private Schedule schedule;


    public Schedule getSchedule() {
    
    
        return schedule;
    }


    public void setSchedule(Schedule schedule) {
    
    
        this.schedule = schedule;
    }

    @Override
    public  void run() {
    
    
        this.task();
    }

    //执行的任务
    public abstract void  task();


    @Override
    public String toString() {
    
    
        return schedule.getCronId()+"   "+schedule.getCronKey();
    }

}

4 编写动态任务配置类

@Configuration
@EnableScheduling
public class DynamicTask implements SchedulingConfigurer {
    
    
    private static Logger LOGGER = LoggerFactory.getLogger(DynamicTask.class);

    //通过 registrar 可以注册 定时任务
    private volatile ScheduledTaskRegistrar registrar;


    //存放执行器
    private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();

    //存放当前执行的定时任务
    private final ConcurrentHashMap<Integer, CronTask> cronTasks = new ConcurrentHashMap<>();


    @Autowired
    private ScheduleMapper scheduleMapper;

    @Autowired
    private ApplicationContext applicationContext;



    @Override
    public void configureTasks(ScheduledTaskRegistrar registrar) {
    
    
        this.registrar = registrar;


        this.registrar.addTriggerTask(() -> {
    
    
            //查询定时任务
            List<Schedule> scheduleList = scheduleMapper.getAll();
            createTask(scheduleList);
        }
        , triggerContext -> new PeriodicTrigger(5L, TimeUnit.SECONDS).nextExecutionTime(triggerContext));

    }


    public void createTask(List<Schedule> scheduleList){
    
    

        if (!CollectionUtils.isEmpty(scheduleList)) {
    
    
            LOGGER.info("检测动态定时任务列表...");
            List<TimingTask> tts = new ArrayList<>();

            scheduleList .forEach(taskConstant -> {
    
    
                Object bean = null;
                try {
    
    
                    Class<?> taskClass = Class.forName(taskConstant.getCronKey());
                    //创建定时任务 去执行
                    bean = applicationContext.getBean(taskClass);
                } catch (ClassNotFoundException e) {
    
    
                    LOGGER.info("定时任务执行器{} 在Spring内找不到!",taskConstant.getCronKey());
                    return;
                }

                //获取bean后转换未 TimingTask 对象
                if(bean instanceof  TimingTask){
    
    
                    TimingTask timingTask =  (TimingTask)bean;
                   timingTask.setSchedule(taskConstant);
                    tts.add(timingTask);
                }
            });
            this.refreshTasks(tts.toArray(new TimingTask[tts.size()]));
        }
        LOGGER.info("定时任务加载完成!");

    }




    private void refreshTasks(TimingTask... tasks) {
    
    

        Set<Integer> taskIds = scheduledFutures.keySet();

        //查看任务是否已经存在,如果存在了就取消
        for (Integer taskId : taskIds) {
    
    
            if (!exists(taskId,tasks)) {
    
    
                scheduledFutures.get(taskId).cancel(false);
            }
        }

        for (TimingTask timingTask : tasks) {
    
    

            //校验 cron 表达式是否合法
            String expression = timingTask.getSchedule().getCronExpression();
            if (StringUtils.isBlank(expression) || !CronSequenceGenerator.isValidExpression(expression)) {
    
    
                LOGGER.error("定时任务"+timingTask.getSchedule().getCronKey()+" cron表达式不合法: " + expression);
                continue;
            }


            //如果配置一致,并且处于启用状态 则不需要重新创建定时任务
            if (scheduledFutures.containsKey(timingTask.getSchedule().getCronId())
                    && cronTasks.get(timingTask.getSchedule().getCronId()).getExpression().equals(expression) && timingTask.getSchedule().getStatus().equals("1")) {
    
    
                continue;
            }


            //如果策略执行时间发生了变化(如cron表达式修改,状态修改),则取消当前策略的任务,重新创建
            if (scheduledFutures.containsKey(timingTask.getSchedule().getCronId())) {
    
    
                scheduledFutures.remove(timingTask.getSchedule().getCronId()).cancel(false);
                cronTasks.remove(timingTask.getSchedule().getCronId());
            }

            //如果任务有效,才创建
           if(timingTask.getSchedule().getStatus().equals("1")){
    
    
               //创建定时任务执行
               CronTask task = new CronTask(timingTask, expression);
               ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
               cronTasks.put(timingTask.getSchedule().getCronId(), task);

               scheduledFutures.put(timingTask.getSchedule().getCronId(), future);
               LOGGER.info("添加定时任务===>{}   Cron表达式==>{}",timingTask.getSchedule().getCronKey(),timingTask.getSchedule().getCronExpression());
           }
        }
    }

    private boolean exists(Integer taskId,TimingTask... tasks) {
    
    
        for (TimingTask task : tasks) {
    
    
            if (task.getSchedule().getCronId().equals(taskId)) {
    
    
                return true;
            }
        }
        return false;
    }



    @PreDestroy
    public void destroy() {
    
    
        this.registrar.destroy();
    }
}

大功告成,启动测试:

在数据库内插入数据,定时任务能正常运行,并且在不重启服务器的情况下通过改变数据库的 cron表达式或状态能够控制定时任务的启动和停止,修改定时任务执行频率


GitHub地址:Spring-Boot-Schedule

参考了几篇大佬的博客:希望同样对您有用:

Springboot定时任务原理及如何动态创建定时任务

在Spring Boot中动态实现定时任务配置

猜你喜欢

转载自blog.csdn.net/dndndnnffj/article/details/109674404