基于数据库的动态定时任务

18 篇文章 13 订阅
5 篇文章 0 订阅


1.实现原理

  1. @EnableScheduling开启任务调度;
  2. 自定义调度线程池;
  3. 数据库定义定时任务;
  4. 启动完成时,加载数据库定时任务并提交到线程池;
  5. 调用接口触发线程池中任务的更新(新增、删除、修改);

2.源码解析

数据库表

CREATE TABLE `timed_task` (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 NOT NULL COMMENT '任务名称',
  `type` tinyint(1) NOT NULL COMMENT '类型:1 cron表达式、2 fixedRate固定速率、 3 fixedDelay固定延迟',
  `value` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '值:type=1时,值为cron表达式;type=2或3时,值为时间,单位秒',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '任务状态:0关闭、1开启',
  `classpath` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '类路径',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定时任务表';

在这里插入图片描述

实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="TimedTask对象", description="定时任务表")
public class TimedTask implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    private Long id;

    @ApiModelProperty(value = "任务名称")
    private String name;

    @ApiModelProperty(value = "类型:1 cron表达式、2 fixedRate固定速率、 3 fixedDelay固定延迟")
    @TableField("`type`")
    private Integer type;

    @ApiModelProperty(value = "值:type=1时,值为cron表达式;type=2或3时,值为时间,单位秒")
    private String value;

    @ApiModelProperty(value = "任务状态:0关闭、1开启")
    private Integer status;

    @ApiModelProperty(value = "类路径")
    private String classpath;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "更新时间")
    private Date updateTime;
}

任务类

定义了两个任务:Tset1Task、Tset2Task类。

Tset1Task类

@Slf4j
public class Tset1Task implements Runnable{
    @Override
    public void run() {
        log.info("========== "+ DateUtil.formatDateTime(new Date()) +" [Tset1]执行开始 ==========");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("========== "+ DateUtil.formatDateTime(new Date()) +" [Tset1]执行完成 ==========");
    }
}

Tset2Task类

@Slf4j
public class Tset2Task implements Runnable{
    @Override
    public void run() {
        log.info("========== "+ DateUtil.formatDateTime(new Date()) +" [Tset2] ==========");
    }
}

调度配置类

ScheduledConfig类实现SchedulingConfigurer接口

@Data
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    // 调度任务注册器
    private ScheduledTaskRegistrar taskRegistrar;
    // 调度线程池存在的任务
    private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>(16);
    // 调度线程池存在的任务
    private final ConcurrentHashMap<Long, TimedTask> tasks = new ConcurrentHashMap<>(16);

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
        // 自定义调度任务线程池
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3));
    }

    /**
     * 刷新全部任务
     * @param list
     */
    public void refreshAllTask(List<TimedTask> list) {
        // 移除已删除的任务
        Set<Long> ids = scheduledFutures.keySet();
        for (Long id : ids) {
            if (!exist(id, list)) {
                scheduledFutures.get(id).cancel(false);
                scheduledFutures.remove(id);
                tasks.remove(id);
            }
        }
        for (TimedTask timedTask : list) {
            refreshTask(timedTask);
        }
    }

    /**
     * 刷新单个任务
     * @param timedTask
     */
    public void refreshTask(TimedTask timedTask) {
        Long id = timedTask.getId();
        if (timedTask.getStatus() == 0) {
            // 删除任务
            if (scheduledFutures.containsKey(timedTask.getId())) {
                scheduledFutures.get(id).cancel(false);
                scheduledFutures.remove(id);
                tasks.remove(id);
            }
            return;
        }
        // 调度任务已存在且任务未发生变化-不做处理
        if (scheduledFutures.containsKey(id) && tasks.get(id).getType().equals(timedTask.getType()) && tasks.get(id).getValue().equals(timedTask.getValue())) {
            return;
        }
        // 调度任务已存在且cron表达式但任务发生了变化-删除原任务
        if (scheduledFutures.containsKey(id) && (!tasks.get(id).getType().equals(timedTask.getType()) || !tasks.get(id).getValue().equals(timedTask.getValue()))) {
            scheduledFutures.get(id).cancel(false);
            scheduledFutures.remove(id);
            tasks.remove(id);
        }
        // 添加任务
        ScheduledFuture<?> schedule = taskRegistrar.getScheduler().schedule(getRunnable(timedTask), getTrigger(timedTask));
        scheduledFutures.put(id,schedule);
        tasks.put(id, timedTask);
    }

    /**
     * 任务是否仍存在于数据库定时任务列表
     * @param id
     * @param timedTasks
     * @return
     */
    private boolean exist(Long id, List<TimedTask> timedTasks) {
        Optional<TimedTask> first = timedTasks.stream().filter(timedTask -> timedTask.getId() == id).findFirst();
        return first.isPresent();
    }

    /**
     * 得到任务运行对象
     * @param timedTask
     * @return
     */
    private Runnable getRunnable(TimedTask timedTask) {
        Runnable runnable = null;
        try {
            Class<?> c = Class.forName(timedTask.getClasspath());
            runnable = (Runnable)c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return runnable;
    }

    /**
     * 得到触发器对象
     * @param timedTask
     * @return
     */
    private Trigger getTrigger(TimedTask timedTask) {
        return new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                Integer type = timedTask.getType();
                Date nextExec;
                if (type == 1) {
                    CronTrigger trigger = new CronTrigger(timedTask.getValue());
                    nextExec = trigger.nextExecutionTime(triggerContext);
                } else {
                    PeriodicTrigger periodicTrigger = new PeriodicTrigger(Long.parseLong(timedTask.getValue()), TimeUnit.SECONDS);
               		periodicTrigger.setFixedRate(type == 2 ? true : false);
					periodicTrigger.setInitialDelay(Long.parseLong(timedTask.getValue()));
                    nextExec = periodicTrigger.nextExecutionTime(triggerContext);
                }
                return nextExec;
            }
        };
    }
}

操作数据库Service

public interface TimedTaskService extends IService<TimedTask> {

    /**
     * 获取全部定时任务(启用)
     * @return
     */
    List<TimedTask> getAll();
}

定时任务初始化类

MyApplicationRunner类实现ApplicationRunner接口

@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {

    @Autowired
    private ScheduledConfig scheduledConfig;
    @Autowired
    private TimedTaskService timedTaskService;
    @Override
    public void run(ApplicationArguments args) {
        log.info("================定时任务初始化-开始================");
        scheduledConfig.refreshAll(timedTaskService.getAll());
        log.info("================定时任务初始化-完成================");
    }
}

刷新定时任务接口类

@RestController
@RequestMapping("/timedTask")
public class TimedTaskController {

    @Autowired
    private ScheduledConfig scheduledConfig;
    @Autowired
    private TimedTaskService timedTaskService;

    @PutMapping("refreshAll")
    public String refreshAllTask() {
        scheduledConfig.refreshAllTask(timedTaskService.getAll());
        return "操作成功";
    }

    @PostMapping("addTask")
    public String addTask(@RequestBody TimedTask timedTask) {
        timedTask.setId(IDUtil.getId());
        timedTaskService.save(timedTask);
        scheduledConfig.refreshTask(timedTask);
        return "操作成功";
    }

    @PutMapping("updateTask")
    public String updateTask(@RequestBody TimedTask timedTask) {
        timedTaskService.updateById(timedTask);
        scheduledConfig.refreshTask(timedTaskService.getById(timedTask.getId()));
        return "操作成功";
    }

    @DeleteMapping("deleteTask")
    public String deleteTask(Long id) {
        TimedTask timedTask = timedTaskService.getById(id);
        if (timedTask != null) {
            timedTask.setStatus(0);
            timedTaskService.updateById(timedTask);
            scheduledConfig.refreshTask(timedTask);
        }
        return "操作成功";
    }
}

启动类

@SpringBootApplication
@EnableScheduling
public class SpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }

}

3.注意事项

  1. 该方式的动态定时任务不支持分布式系统(每台服务都会执行),通过加分布式锁等方式可以支持分布式系统。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值