无中生有 之 从0开始写一个动态定时任务管理

前言

你是否为@Scheduled注解不能动态修改而苦恼?是否在为公司内网框架无法引入外部开源定时任务而焦虑?不需要难过了,你的强来啦!

注:本文旨在通过Java Spring内置的Cron功能实现异步定时任务的处理,适用于大部分中小型项目需求。大型需求需自行寻找更合适的定时任务框架,本文作者未试验大型需求是否支持。

环境准备

  • 开发工具与依赖:JDK 8+、Maven、Idea
    在Application类上添加@EnableScheduling注解,以启用Spring的定时任务支持:
java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

代码编写

1、定时任务封装类

java 复制代码
import lombok.Data;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.CronTrigger;

import java.util.concurrent.ScheduledFuture;

/**
 * 定时任务封装类
 * 包含任务的基本信息和执行状态
 *
 * @author CHENG RONG YU
 * @since 2026-04-29
 */
@Data
public class ScheduledTask {
	// 任务ID
	private Long taskId;
	// 任务名称
	private String taskName;
	// 任务执行逻辑
	private Runnable task;
	// 任务触发器
	private Trigger trigger;
	// 任务执行Future对象,用于控制任务
	private ScheduledFuture<?> future;

	/**
	 * 构造方法
	 *
	 * @param taskId 任务ID
	 * @param taskName 任务名称
	 * @param task 任务执行逻辑
	 * @param cronExpression Cron表达式
	 */
	public ScheduledTask(Long taskId, String taskName, Runnable task, String cronExpression) {
		this.taskId = taskId;
		this.taskName = taskName;
		this.task = task;
		this.trigger = new CronTrigger(cronExpression);
	}
}

2、线程池任务调度器配置类

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * 线程池任务调度器配置类
 * 用于支持动态定时任务的执行
 *
 * @author CHENG RONG YU
 * @since 2026-04-29
 */
@Configuration
public class TaskSchedulerConfig {

	/**
	 * 配置线程池任务调度器
	 *
	 * @return ThreadPoolTaskScheduler实例
	 */
	@Bean
	public ThreadPoolTaskScheduler taskScheduler() {
		ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
		// 设置线程池大小
		scheduler.setPoolSize(10);
		// 设置线程名称前缀
		scheduler.setThreadNamePrefix("dynamic-schedule-");
		// 设置关闭时等待任务完成的最大秒数
		scheduler.setAwaitTerminationSeconds(60);
		// 设置关闭时等待正在执行的任务完成
		scheduler.setWaitForTasksToCompleteOnShutdown(true);
		return scheduler;
	}
}

3、线程池任务调度器配置类

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * 动态定时任务管理器
 * 支持运行时动态添加、修改、删除定时任务
 *
 * @author CHENG RONG YU
 * @since 2026-04-29
 */
@Slf4j
@Configuration
public class DynamicScheduleManager {

	@Autowired
	private ThreadPoolTaskScheduler taskScheduler;

	// 存储所有已注册的定时任务,key为任务ID
	private final Map<Long, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();

	/**
	 * 初始化方法
	 */
	@PostConstruct
	public void init() {
		log.info("动态定时任务管理器初始化完成");
		log.info("ThreadPoolTaskScheduler 状态: {}", taskScheduler != null ? "已注入" : "未注入");
	}

	/**
	 * 添加定时任务
	 * 如果任务ID已存在,会先移除旧任务再添加新任务
	 *
	 * @param taskId 任务ID
	 * @param taskName 任务名称
	 * @param task 任务执行逻辑
	 * @param cronExpression Cron表达式
	 */
	public void addTask(Long taskId, String taskName, Runnable task, String cronExpression) {
		log.info("准备添加定时任务 - 任务ID: {}, 任务名称: {}, Cron: {}", taskId, taskName, cronExpression);

		// 验证Cron表达式
		try {
			CronTrigger trigger = new CronTrigger(cronExpression);
			Date nextExecution = trigger.nextExecutionTime(new org.springframework.scheduling.TriggerContext() {
				@Override
				public Date lastScheduledExecutionTime() {
					return new Date();
				}
				@Override
				public Date lastActualExecutionTime() {
					return new Date();
				}
				@Override
				public Date lastCompletionTime() {
					return new Date();
				}
			});
			log.info("下次执行时间: {}", nextExecution);
		} catch (Exception e) {
			log.error("Cron表达式无效 - 任务ID: {}, Cron: {}, 错误: {}", taskId, cronExpression, e.getMessage());
			return;
		}

		// 先移除已存在的同ID任务
		removeTask(taskId);
		try {
			// 创建Cron触发器
			CronTrigger trigger = new CronTrigger(cronExpression);
			// 调度任务并获取Future对象
			ScheduledFuture<?> future = taskScheduler.schedule(task, trigger);

			if (future == null) {
				log.error("任务调度失败,返回null - 任务ID: {}", taskId);
				return;
			}

			// 封装任务信息
			ScheduledTask scheduledTask = new ScheduledTask(taskId, taskName, task, cronExpression);
			scheduledTask.setFuture(future);
			scheduledTask.setTrigger(trigger);
			// 存入任务映射表
			scheduledTasks.put(taskId, scheduledTask);
			log.info("定时任务添加成功 - 任务ID: {}, 任务名称: {}, Cron表达式: {}, 当前任务总数: {}",
				taskId, taskName, cronExpression, scheduledTasks.size());
		} catch (Exception e) {
			log.error("定时任务添加失败 - 任务ID: {}, 错误信息: {}", taskId, e.getMessage(), e);
		}
	}

	/**
	 * 移除定时任务
	 * 会取消任务的执行并从映射表中删除
	 *
	 * @param taskId 任务ID
	 */
	public void removeTask(Long taskId) {
		ScheduledTask scheduledTask = scheduledTasks.remove(taskId);
		if (scheduledTask != null && scheduledTask.getFuture() != null) {
			// 取消任务执行(尝试中断正在执行的任务)
			boolean cancelled = scheduledTask.getFuture().cancel(true);
			log.info("定时任务已移除 - 任务ID: {}, 取消结果: {}, 是否已完成: {}",
				taskId, cancelled, scheduledTask.getFuture().isDone());
		}
	}

	/**
	 * 更新定时任务的Cron表达式
	 * 先移除旧任务,再用新的Cron表达式重新添加
	 *
	 * @param taskId 任务ID
	 * @param cronExpression 新的Cron表达式
	 */
	public void updateTask(Long taskId, String cronExpression) {
		ScheduledTask scheduledTask = scheduledTasks.get(taskId);
		if (scheduledTask != null) {
			// 移除旧任务
			removeTask(taskId);
			// 用新Cron表达式重新添加
			addTask(taskId, scheduledTask.getTaskName(), scheduledTask.getTask(), cronExpression);
			log.info("定时任务已更新 - 任务ID: {}, 新Cron表达式: {}", taskId, cronExpression);
		} else {
			log.warn("定时任务不存在,无法更新 - 任务ID: {}", taskId);
		}
	}

	/**
	 * 取消定时任务执行
	 * 与removeTask不同,此方法只取消执行,不从映射表中删除
	 *
	 * @param taskId 任务ID
	 */
	public void cancelTask(Long taskId) {
		ScheduledTask scheduledTask = scheduledTasks.get(taskId);
		if (scheduledTask != null && scheduledTask.getFuture() != null) {
			scheduledTask.getFuture().cancel(false);
			log.info("定时任务已取消 - 任务ID: {}", taskId);
		}
	}

	/**
	 * 恢复已取消的定时任务
	 * 使用原有的Cron表达式重新启动任务
	 *
	 * @param taskId 任务ID
	 */
	public void resumeTask(Long taskId) {
		ScheduledTask scheduledTask = scheduledTasks.get(taskId);
		if (scheduledTask != null) {
			CronTrigger trigger = (CronTrigger) scheduledTask.getTrigger();
			ScheduledFuture<?> future = taskScheduler.schedule(scheduledTask.getTask(), trigger);
			scheduledTask.setFuture(future);
			log.info("定时任务已恢复 - 任务ID: {}", taskId);
		}
	}

	/**
	 * 获取所有定时任务
	 *
	 * @return 所有定时任务的映射表副本
	 */
	public Map<Long, ScheduledTask> getAllTasks() {
		return new ConcurrentHashMap<>(scheduledTasks);
	}

	/**
	 * 获取指定ID的定时任务
	 *
	 * @param taskId 任务ID
	 * @return 定时任务对象,不存在则返回null
	 */
	public ScheduledTask getTask(Long taskId) {
		return scheduledTasks.get(taskId);
	}
}

4、日常刷新定时任务及调用

java 复制代码
	/**
	 * 应用启动时初始化定时任务
	 * 从数据库加载所有未删除且配置了Cron表达式的任务,并注册到定时任务管理器
	 */
	@PostConstruct
	public void initScheduledTasks() {
		log.info("开始加载数据导入定时任务...");
		// 构建查询条件 从数据库同步定时任务
		......
		// 查询符合条件的任务列表
		List<Rwxx> taskList = baseMapper.selectList(queryWrapper);
		// 遍历任务列表,逐个注册到定时任务管理器
		for (Rwxx task : taskList) {
			try {
				Long taskId = task.getId();
				String cronExpression = task.getCronBds();
				String taskName = task.getSjyMc();
				// 创建任务执行逻辑
				Runnable taskRunnable = () -> {
					try {
						xxx(taskId);//定时任务的具体执行逻辑
					} catch (InterruptedException e) {
						throw new RuntimeException(e);
					}
				};
				// 注册定时任务
				scheduleManager.addTask(taskId, taskName, taskRunnable, cronExpression);
				log.info("加载定时任务成功 - ID: {}, 名称: {}, Cron: {}", taskId, taskName, cronExpression);
			} catch (Exception e) {
				log.error("加载定时任务失败 - ID: {}, 错误: {}", task.getId(), e.getMessage(), e);
			}
		}
		log.info("定时任务加载完成,共加载 {} 个任务", taskList.size());
	}
相关推荐
techdashen1 小时前
dial9:给 Tokio 装上“飞行记录仪“
java·数据库·redis
Dxy12393102162 小时前
Python 去除 HTML 标签获取纯文本
开发语言·python·html
ShiJiuD6668889992 小时前
springboot基础篇
java·spring boot·spring
砚底藏山河2 小时前
python、JavaScript 、JAVA,定制化数据服务,助力业务高效落地
java·javascript·python
qq_452396232 小时前
第六篇:《JMeter逻辑控制器:循环、条件和交替执行》
android·java·jmeter
洛的地理研学2 小时前
Python下载并处理MOD13A3植被指数数据
开发语言·python
humcomm2 小时前
Java 新特性2026年5月速览
java·开发语言
xiao_li_ya2 小时前
C++学习日记1(`*`的理解、const关键词)
开发语言·c++
码力斜杠哥2 小时前
Rust初习录(6)Rust的 if 玩法
开发语言·python·rust