关于SpringBoot动态定时任务的实现

简单介绍
  1. 通过Spring自己的任务调度包Task实现定时任务的周期动态设置,以及随时启动停止等操作
  2. 需要注意的是,只能单机使用,集群和分布式结构不要考虑,否则会多次执行。可以考虑任务调度框架Quartz,听说很好用, 我还没用过 - -
怎么用
  1. 首先搞个存储设置信息的表
CREATE TABLE `sys_timed_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `task_sort` int(11) DEFAULT NULL COMMENT '定时任务序号 (两位一组 例:1020)',
  `task_path` varchar(100) DEFAULT NULL COMMENT '定时任务执行类路径',
  `task_cron` varchar(50) DEFAULT NULL COMMENT '执行周期(cron表达式)',
  `is_del` int(2) DEFAULT '10' COMMENT '是否可删除或停用 10|否/20|是 (立刻生效)',
  `creater_name` varchar(50) DEFAULT NULL COMMENT '创建人',
  `create_time` varchar(20) DEFAULT NULL COMMENT '创建时间',
  `enabled` int(2) DEFAULT '10' COMMENT '是否可用 10|可用/20|停用 (立刻生效)',
  `remark` varchar(100) DEFAULT NULL COMMENT '备注-预留',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务配置表';
  1. 定时配置类
package com.runlin.wework.config;

import com.runlin.wework.entity.SysTimedTaskEntity;
import com.runlin.wework.task.dao.SysTimedTaskDao;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.Task;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Description:
 * 动态定时任务配置类
 * <p>
 * ClassName: TimedTaskConfig
 * date: 2020/10/22 13:16
 *
 * @author jo.li
 * @version 1.0
 * @since JDK 1.8
 */
@Configuration
public class TimedTaskConfig implements SchedulingConfigurer {
    @Resource
    private ApplicationContext context;
    @Resource
    private SysTimedTaskDao timedTaskDao;


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    	// 这里也可以不用selectAll(),自己写个条件查询提前把禁用或已删除数据提出也可以
        for(SysTimedTaskEntity timedTaskEntity : timedTaskDao.selectAll()){
            Class<?> clazz;
            Object task;
            try {
            	// 根据类路径获取实例
                clazz = Class.forName(timedTaskEntity.getTaskPath());
                // 生成bean(放spring里)
                task = context.getBean(clazz);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("sys_timed_task表数据" + timedTaskEntity.getTaskPath() + "有误", e);
            } catch (BeansException e) {
                throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "不是SpringBean", e);
            }
            Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类没有实现ScheduledOfTask接口");
            // 可以通过改变数据库数据进而实现动态改变执行周期
            taskRegistrar.addTriggerTask(((Runnable) task),
                    triggerContext -> {
                        String cronExpression = timedTaskEntity.getTaskCron();
                        return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
                    }
            );
            // 手动创建线程池,防止SchedulingConfigurer导致系统线程阻塞
            taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(10, new ThreadFactory() {
                int counter = 0;
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r,"数据统计-Thread-"+counter);
                    counter++;
                    return thread;
                }
            }));
        }
    }
	/**
     * 搞个池子给Spring,初始的定时放这里执行 
     * @return: java.util.concurrent.Executor
     * @Author jo.li
     * @Description taskExecutor <br/>
     * @Date 2020/11/11 17:30
     **/
//    @Bean
//    public Executor taskExecutor() {
//        // 核心线程数根据定时数量和执行效率来
//        return new ScheduledThreadPoolExecutor(10, new ThreadFactory() {
//            int counter = 0;
//            @Override
//            public Thread newThread(Runnable r) {
//                Thread thread = new Thread(r,"数据统计-Thread-"+counter);
//                counter++;
//                return thread;
//            }
//        });
//    }
}

此处需要注意,在addTriggerTask方法之后一定要指定个线程池给ScheduledTaskRegistrar,不然其他的ApplicationRunner与CommandLineRunner将无法执行!!!
详见:由于通过SchedulingConfigurer实现动态定时,导致的ApplicationRunner无效解决办法
3. 定时实现接口

package com.runlin.wework.config;

import com.runlin.wework.common.GlobalConstant;
import com.runlin.wework.entity.SysTimedTaskEntity;
import com.runlin.wework.task.dao.SysTimedTaskDao;
import com.runlin.wework.utils.SpringUtils;

/**
 * Description:
 * <p>
 * ClassName: ScheduledOfTask
 * date: 2020/10/22 13:23
 *
 * @author jo.li
 * @version 1.0
 * @since JDK 1.8
 */
public interface ScheduledOfTask extends Runnable {
    /**
     * 定时任务业务实现方法
     *
     * @return: void
     * @Author jo.li
     * @Description execute <br/>
     * @Date 2020/10/22 14:43
     **/
    void execute();

    /**
     * 实现控制定时任务启用或禁用的功能
     *
     * @return: void
     * @Author jo.li
     * @Description run <br/>
     * @Date 2020/10/22 14:12
     **/
    @Override
    default void run() {
        SysTimedTaskDao timedTaskDao = SpringUtils.getBean(SysTimedTaskDao.class);
        SysTimedTaskEntity timedTaskEntity = timedTaskDao.getTimedTaskByPath(this.getClass().getName());
        // 禁用或已删除
        if (GlobalConstant.NO_ENABLED.equals(timedTaskEntity.getEnabled()) ||
                GlobalConstant.IS_DEL.equals(timedTaskEntity.getIsDel())) {
            return;
        }
        // 执行
        execute();
    }
}
  1. 定时任务类使用执行类添加注解@Component 实现接口ScheduledOfTask就OK了
package com.runlin.wework.task.data;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Description:
 * 各类问题咨询人数统计
 * <p>
 * ClassName: QuestionPerDayStatistics
 * date: 2020/10/23 14:29
 *
 * @author jo.li
 * @version 1.0
 * @since JDK 1.8
 */
@Component
public class QuestionPerDayStatistics implements ScheduledOfTask {
    @Resource
    private DataStatisticsUtils statisticsUtils;
    private final Logger log = LoggerFactory.getLogger(getClass());
    /**
     * 统计初始值
     */
    private final String INIT_NUM = "1";

    /**
     * 定时任务业务实现方法
     *
     * @return: void
     * @Author jo.li
     * @Description execute <br/>
     * @Date 2020/10/22 14:43
     **/
    @Override
    public void execute() {
        log.info("各类问题咨询人数统计定时开始运行!");
        
        log.info("各类问题咨询人数统计定时运行结束!");
    }
}
  1. 页面启用停用有时间再说。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暴躁码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值