⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7322518787423748106 「不爱总结的麦穗」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

最近在做一个项目,需要用到动态定时任务,现在比较普遍的做法是集成第三方框架(例如Quartz、XXL-JOB),我自己在做这个项目的时候也考虑过去集成Quartz实现,但是基于项目本身的复杂度和使用场景放弃了

本文主要分享在不依赖过多的其他框架,使用springBoot自身带有的定时任务框架来实现动态定时任务

注解实现定时任务

具体实现

主要基于@EnableScheduling和 @Scheduled注解

  • 主启动类上加上 @EnableScheduling 注解
  • 写一个类,注入到容器中,在方法上加上 @Scheduled 注解
@Slf4j
@Component
public class TimeTask {

@Scheduled(cron = "0 0/31 * * * ?")
public void refresh(){
log.info("//// 定时刷新");

//业务代码
}

}

这样就实现了定时任务,是不是很简单?不难发现,这种方式定时执行时间是固定的,但是大部分业务的定时执行时间是经常在变化的,这时候我们就需要通过动态定时任务实现

实现动态定时任务

Spring实现动态定时任务的核心就是其提供的任务调度类ThreadPoolTaskScheduler,ThreadPoolTaskScheduler基于线程池来执行任务,可以按照固定的时间间隔或者指定的Cron表达式来调度任务的执行。

图片

在我的项目中主要用到了ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法,指定的Cron表达式来调度任务的执行

具体实现

创建ThreadPoolTaskScheduler配置类

@Configuration
public class SchedulingConfig {

@Bean
public TaskScheduler taskScheduler() {
// 获取系统处理器个数, 作为线程池数量
int corePoolSize = Runtime.getRuntime().availableProcessors();
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
taskScheduler.setPoolSize(corePoolSize);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("AntiFraudSchedulerThreadPool-");
return taskScheduler;
}

}

创建ScheduledTask包装类

ScheduledFuture是ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法的返回值。

ScheduledFuture继承了Future接口,Future接口提供一组辅助方法,比如:

  • cancel():取消任务
  • isCancelled():任务是不是取消了
  • isDone():任务是不是已经完成了
  • get():用来获取执行结果

当我们调用cancel方法时,会将我们的任务从workQueue中移除

public final class ScheduledTask {

public volatile ScheduledFuture<?> future;

/**
* 取消定时任务
*/
public void cancel() {
ScheduledFuture<?> scheduledFuture = this.future;
if (Objects.nonNull(scheduledFuture)) {
scheduledFuture.cancel(true);
}
}
}

创建CronTaskRegistrar类

实现了DisposableBean接口的类,用于注册定时任务。它具有添加、删除和调度定时任务的方法。在销毁时,会取消所有定时任务。

@Slf4j
@Component
@SuppressWarnings("all")
public class CronTaskRegistrar implements DisposableBean {

@Resource
private TaskScheduler taskScheduler;

// 保存任务Id和定时任务
private final Map<String, ScheduledTask> scheduledTaskMap = new ConcurrentHashMap<>(64);

// 添加任务
public void addTask(Runnable task, String cronExpression,String jobId) {
addTask(new CronTask(task, cronExpression),jobId);
}

public void addTask(CronTask cronTask,String jobId) {
if (Objects.nonNull(cronTask)) {
Runnable task = cronTask.getRunnable();
if (this.scheduledTasks.containsKey(task)) {
removeTask(jobId);
}
// 保存任务Id和定时任务
this.scheduledTaskMap.put(jobId, scheduleTask(cronTask));
}
}

// 通过任务Id,取消定时任务
public void removeTask(String jobId) {
ScheduledTask scheduledTask = this.scheduledTaskMap.remove(jobId);
if (Objects.nonNull(scheduledTask)) {
scheduledTask.cancel();
}
}

public ScheduledTask scheduleTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}

// 销毁
@Override
public void destroy() {
this.scheduledTaskMap.values().forEach(ScheduledTask::cancel);
this.scheduledTaskMap.clear();
}
}

动态定时任务的核心逻辑到这基本就已经完成了,具体的Service类代码,这里就不贴出来了,因为里面基本上都是业务逻辑和CronTaskRegistrar类的编排(ps:需要的也可以私聊demo)

定时任务表设计

create table schedule_setting
(
id varchar(32) not null comment '唯一id'
primary key,
job_id varchar(64) null comment '任务ID',
cron_expression varchar(255) null comment 'cron表达式',
job_result varchar(32) null comment '任务结果(通过 复议 拒绝)',
create_date datetime null comment '创建时间',
status varchar(4) null,
create_by varchar(64) null comment '创建人账号',
creator varchar(64) null comment '创建人',
version bigint(19) null comment '版本号'
)
comment '定时任务表';

项目启动时加载所有任务

@Component
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class ScheduleRunner implements CommandLineRunner {


ScheduleSettingRepository scheduleSettingRepository;

CronTaskRegistrar scheduledTaskRegistrar;

@Override
public void run(String... args) throws Exception {
// 查询所有定时任务
List<ScheduleSetting> scheduleSettingList=scheduleSettingRepository.findByStatus(EnableFlag.Y.name());
for (ScheduleSetting scheduleSetting : scheduleSettingList) {
//调用CronTaskRegistrar添加任务方法
scheduledTaskRegistrar.addCronTask(() -> {
···
任务执行方法
...
}, scheduleSetting.getCronExpression(), scheduleSetting.getJobId());
}

}
}

至此,动态定时任务就说完啦。

文章目录
  1. 1. 注解实现定时任务
    1. 1.1. 具体实现
  2. 2. 实现动态定时任务
    1. 2.1. 具体实现