1.实现原理
- @EnableScheduling开启任务调度;
- 自定义调度线程池;
- 数据库定义定时任务;
- 启动完成时,加载数据库定时任务并提交到线程池;
- 调用接口触发线程池中任务的更新(新增、删除、修改);
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.注意事项
- 该方式的动态定时任务不支持分布式系统(每台服务都会执行),通过加分布式锁等方式可以支持分布式系统。