背景
实现对定时任务的开启关闭,对定时任务实现,不同参数,不同cron表达式。
spring boot使用TaskScheduler实现动态增删启停定时任务
添加执行定时任务的线程池配置类 初始化TaskScheduler
/**
* @description 添加执行定时任务的线程池配置类 初始化TaskScheduler
* @date 2023/9/6 9:06
*/
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
taskScheduler.setPoolSize(4);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
return taskScheduler;
}
}
添加定时任务注册类,用来增加、删除定时任务。
/**
*
* @description 添加定时任务注册类,用来增加、删除定时任务。
* @date 2023/9/6 8:52
*/
@Component
public class CronTaskRegistrar implements DisposableBean {
private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
private final Map<String, ScheduleResult> schedulerJob = new HashMap<>();
@Autowired
private TaskScheduler taskScheduler;
public TaskScheduler getScheduler() {
return this.taskScheduler;
}
public void addCronTask(ScheduleResult scheduleResult) {
SchedulingRunnable task = new SchedulingRunnable(scheduleResult.getBeanName(), scheduleResult.getMethodName(), scheduleResult.getMethodParams());
String cronExpression = scheduleResult.getCronExpression();
CronTask cronTask = new CronTask(task, cronExpression);
// 如果当前包含这个任务,则移除
if (this.scheduledTasks.containsKey(task)) {
removeCronTask(scheduleResult.getBeanName(), scheduleResult.getMethodName(), scheduleResult.getMethodParams());
}
schedulerJob.put(scheduleResult.getJobId(), scheduleResult);
this.scheduledTasks.put(task, scheduleCronTask(cronTask));
}
public void removeCronTask(String beanName, String methodName, String methodParams) {
SchedulingRunnable task = new SchedulingRunnable(beanName, methodName, methodParams);
ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
if (scheduledTask != null) {
scheduledTask.cancel();
}
}
public void removeCronTask(ScheduleResult scheduleResult) {
schedulerJob.put(scheduleResult.getJobId(), scheduleResult);
removeCronTask(scheduleResult.getBeanName(), scheduleResult.getMethodName(), scheduleResult.getMethodParams());
}
public ScheduledTask scheduleCronTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
public Map<Runnable, ScheduledTask> getScheduledTasks() {
return scheduledTasks;
}
public Map<String, ScheduleResult> getSchedulerJob() {
return schedulerJob;
}
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks.values()) {
task.cancel();
}
this.scheduledTasks.clear();
}
public ScheduleResult getSchedulerByJobId(String jobId) {
for (ScheduleResult job : findAllTask()) {
if (jobId.equals(job.getJobId())) {
return job;
}
}
return null;
}
public List<ScheduleResult> findAllTask() {
List<ScheduleResult> ScheduleResults = new ArrayList<>();
Set<Map.Entry<String, ScheduleResult>> entries = schedulerJob.entrySet();
for (Map.Entry<String, ScheduleResult> en : entries) {
ScheduleResults.add(en.getValue());
}
return ScheduleResults;
}
}
cheduledFuture的包装类,ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
public final class ScheduledTask {
volatile ScheduledFuture<?> future;
/**
* 取消定时任务
*/
public void cancel() {
ScheduledFuture<?> future = this.future;
if (future != null) {
future.cancel(true);
}
}
}
定时任务的参数实体
@Data
public class ScheduleResult {
/**
* 任务ID
*/
private String jobId;
/**
* bean名称
*/
private String beanName;
/**
* 方法名称
*/
private String methodName;
/**
* 方法参数: 执行service里面的哪一种方法
*/
private String methodParams;
/**
* cron表达式
*/
private String cronExpression;
/**
* 状态(1正常 0暂停)
*/
private Integer jobStatus;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private String createTime;
/**
* 更新时间
*/
private String updateTime;
}
Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法
public class SchedulingRunnable implements Runnable{
private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);
private final String beanName;
private final String methodName;
private final String params;
public SchedulingRunnable(String beanName, String methodName) {
this(beanName, methodName, null);
}
public SchedulingRunnable(String beanName, String methodName, String params) {
this.beanName = beanName;
this.methodName = methodName;
this.params = params;
}
@Override
public void run() {
logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
long startTime = System.currentTimeMillis();
try {
Object target = SpringContextUtils.getBean(beanName);
Method method = null;
if (StringUtils.isNotEmpty(params)) {
method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
method = target.getClass().getDeclaredMethod(methodName);
}
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotEmpty(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
} catch (Exception ex) {
logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
}
long times = System.currentTimeMillis() - startTime;
logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SchedulingRunnable that = (SchedulingRunnable) o;
if (params == null) {
return beanName.equals(that.beanName) &&
methodName.equals(that.methodName) &&
that.params == null;
}
return beanName.equals(that.beanName) &&
methodName.equals(that.methodName) &&
params.equals(that.params);
}
@Override
public int hashCode() {
if (params == null) {
return Objects.hash(beanName, methodName);
}
return Objects.hash(beanName, methodName, params);
}
状态枚举
public enum ScheduleJobStatus {
/**
* 暂停
*/
PAUSE,
/**
* 正常
*/
NORMAL;
}
校验cron表达式合法性
public class CronUtils {
/**
* 返回一个布尔值代表一个给定的Cron表达式的有效性
*
* @param cronExpression Cron表达式
* @return boolean 表达式是否有效
*/
public static boolean isValid(String cronExpression) {
return CronExpression.isValidExpression(cronExpression);
}
}
当集群化部署时,每个节点服务可能存在多个定时任务实例,或者存在定时任务的节点挂掉后,另外的节点无法根据状态进行创建。类似于xxl-job具有的功能。灾备。采用redis缓存或者分布式锁实现。
当某个节点存在定时任务时,会定时扫描数据库里参数状态进行定时任务的删除与新增。
@Component
@Slf4j
public class PatrolPlanInitializing {
@Autowired
private EvtPatrolPlanServiceImpl evtPatrolPlanService;
@Autowired
private ScheduleJobService scheduleJobService;
@Autowired
private RedisService redisService;
@Scheduled(cron = "0 0/10 * ? * ? ")
public void initializingTask(){
EvtPatrolPlan evtPatrolPlan =new EvtPatrolPlan();
evtPatrolPlan.setIsPulse(EvtPatrolPlanServiceImpl.NORMAL);
List<EvtPatrolPlan> evtPatrolPlans = evtPatrolPlanService.selectEvtPatrolPlanList(evtPatrolPlan);
for (EvtPatrolPlan it :evtPatrolPlans){
String key = it.getTaskCode();
EvtPatrolPlan param = evtPatrolPlanService.checkParam(it);
// if (redisService.hasKey(key) && !redisService.hasKey(key+"-heartbeat")){
if (!redisService.hasKey(key+"-heartbeat")){
ScheduleResult scheduleResult = new ScheduleResult();
scheduleResult.setJobId(it.getTaskCode());
scheduleResult.setBeanName(EvtPatrolPlanServiceImpl.BeanName);
scheduleResult.setMethodName(EvtPatrolPlanServiceImpl.AddMethod);
scheduleResult.setMethodParams(JSON.toJSONString(param));
scheduleResult.setCronExpression(it.getCycleCron());
scheduleResult.setJobStatus(EvtPatrolPlanServiceImpl.NORMAL);
scheduleJobService.addScheduleJob(scheduleResult);
redisService.setCacheObject(it.getTaskCode()+"-heartbeat",it,11L, TimeUnit.MINUTES);
log.info("定时任务扫描:------需要开启的巡查任务:{}",scheduleResult.getMethodParams());
}
}
}
@Scheduled(cron = "0 0/10 * ? * ?")
public void checkJob(){
List<ScheduleResult> allTask = scheduleJobService.findAllTask();
List<String> hasJobIds = allTask.stream().map(ScheduleResult::getJobId).collect(Collectors.toList());
log.info("定时任务扫描:------线程节点存在开启状态的巡查任务:{}",JSON.toJSONString(hasJobIds));
EvtPatrolPlan evtPatrolPlan =new EvtPatrolPlan();
evtPatrolPlan.setIsPulse(0);
List<EvtPatrolPlan> evtPatrolPlans = evtPatrolPlanService.selectEvtPatrolPlanList(evtPatrolPlan);
List<String> jobIds = evtPatrolPlans.stream().map(EvtPatrolPlan::getTaskCode).collect(Collectors.toList());
for (ScheduleResult it: allTask){
if (jobIds.contains(it.getJobId())){
scheduleJobService.deleteScheduleJob(it);
redisService.deleteObject(it.getJobId()+"-heartbeat");
log.info("定时任务扫描:------需要关闭的巡查任务:{}",it.getMethodParams());
}
redisService.setCacheObject(it.getJobId()+"-heartbeat",it,11L, TimeUnit.MINUTES);
}
}
}