Springboot中动态管理定时任务

news/2024/9/25 10:42:12

引言

基于cron表达式的定时任务实现,因为cron表达式对于每个任务不确定,所以使用线程池来动态的创建和销毁定时任务

依赖

因为使用的spring自带的调度功能,所以没有额外的依赖,我的项目版本为:Static Badge Static Badge

使用

首先需要定义一个线程池,使用@configuration 注解配置

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.TaskScheduler;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;@Configuration
public class SchedulerConfig {@Beanpublic TaskScheduler taskScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();// 设置线程池大小scheduler.setPoolSize(10);scheduler.setThreadNamePrefix("workshop-scheduled-task-");scheduler.initialize();return scheduler;}
}

这个配置中定义了一个调度的线程池,并且配置了线程池大小、线程名称前缀以及初始化操作

然后实现一个定时管理

import com.google.common.collect.Maps;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.scheduling.TaskScheduler;  
import org.springframework.scheduling.support.CronTrigger;  
import org.springframework.stereotype.Service;  import javax.annotation.Resource;  
import java.time.LocalDateTime;  
import java.time.format.DateTimeFormatter;  
import java.util.Map;  
import java.util.concurrent.ScheduledFuture;@Slf4j
@Service
public class DynamicScheduledTaskService {@Resourceprivate TaskScheduler taskScheduler;/**  * 使用Map来关联任务ID和ScheduledFuture* 如有必要,可以通过Redis等数据库进行管理*/private final Map<String, ScheduledFuture<?>> tasks = Maps.newConcurrentMap();/*** 周期调度执行任务*  * @param taskId         任务ID  * @param cronExpression cron表达式  * @param task           实际任务  */public void schedulingTask(String taskId, String cronExpression, Runnable task) {  log.info("添加定时调度任务:{},cron为:{}", taskId, cronExpression);  // 取消已存在的同ID任务ScheduledFuture<?> existingTask = tasks.get(taskId);if (existingTask != null && !existingTask.isCancelled()) {  existingTask.cancel(false);  }  // 包装任务以便在执行完毕后自动取消  Runnable wrappedTask = () -> {log.info("{} 执行定时调度任务:{}", DateUtil.nowToString(), taskId);  task.run();  };  // 安排任务并保存其Future  ScheduledFuture<?> future = taskScheduler.schedule(wrappedTask, new CronTrigger(cronExpression));  tasks.put(taskId, future);  }  /*** 定时单次执行调度任务*  * @param taskId   任务ID* @param execTime 执行时间* @param task     实际任务*/public void singleScheduleTask(String taskId, LocalDateTime execTime, Runnable task) {log.info("添加定时调度任务:{},执行时间为:{}", taskId, execTime);DateTimeFormatter cronTimeFormatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ?");// 取消已存在的同ID任务ScheduledFuture<?> existingTask = tasks.get(taskId);if (existingTask != null && !existingTask.isCancelled()) {existingTask.cancel(false);}// 包装任务以便在执行完毕后自动取消Runnable wrappedTask = () -> {log.info("{} 执行单次调度任务:{}", LocalDateTime.now(), taskId);try {task.run();} finally {  // 无论任务成功还是异常终止,都取消后续执行  this.stopTask(taskId);  }  };  // 安排任务并保存其Future  ScheduledFuture<?> future = taskScheduler.schedule(wrappedTask, new CronTrigger(cronTimeFormatter.format(execTime)));  tasks.put(taskId, future);}public void stopTask(String taskId) {  ScheduledFuture<?> future = tasks.get(taskId);  if (future != null && !future.isCancelled()) {  future.cancel(false);  tasks.remove(taskId);  log.info("{} 停止调度任务:{}", DateUtil.nowToString(), taskId);  }  }  }

此定时管理服务中一共实现了两种情况

  1. 周期性的执行任务,手动取消才进行取消
  2. 单次执行任务,执行后自动销毁任务

使用服务样例

@Slf4j
@Service
public class UseScheduleService{@Resourceprivate DynamicScheduledTaskService scheduledTaskService;public void startSchedulingTask(){// 其余逻辑String taskId="taskId";// cron表达式,以从0分钟开始,每隔一分钟执行一次为例String crontab = "0 0/1 * * * ?";scheduledTaskService.schedulingTask(taskId, crontab, () -> {// 此处是执行任务的逻辑});}public void startScheduleTask(){// 其余逻辑String taskId="taskId";// 执行的时间LocalDateTime execTime = LocalDateTime.now();scheduledTaskService.scheduleTask(taskId, execTime, () -> {// 此处是执行任务的逻辑});}public void stopTask(String taskId){scheduledTaskService.stopTask(taskId);}}

这样就完成了定时任务的动态管理

定时管理之外

初始化

在使用定时任务的场景中,我们一般还会有重启服务时候,需要针对重启之前已经在执行的任务进行恢复定时,这里我选择使用Spring的 ApplicationRunner 进行服务启动后执行逻辑的管理

import lombok.extern.slf4j.Slf4j;  
import org.springframework.stereotype.Service;
import org.springframework.boot.ApplicationRunner;  
import org.springframework.boot.ApplicationArguments;@Slf4j  
@Service
public class UseScheduleService implements ApplicationRunner{
@Resource
private DynamicScheduledTaskService dynamicScheduledTaskService;@Override  public void run(ApplicationArguments args) {log.info("============= 初始化定时任务begin =============");// 获取需要启动的列表List<Object> needStartList = new ArrayList<>();// 进行定时任务的写入needStartList.forEach(i->dynamicScheduledTaskService.startSchedulingTask(i.getTaskId(), i.getCronTab(),() -> {// 执行的逻辑}));log.info("============= 初始化定时任务end =============");}
}

定时任务工具类

我在使用定时任务时,还遇到了需要判定是否是合规的cron表达式以及根据cron表达式获取下次执行时间的需求,封装的工具类如下

import org.springframework.scheduling.support.CronSequenceGenerator;  import java.time.LocalDateTime;  
import java.time.ZoneId;  
import java.util.Date;public class CronUtils {  private CronUtils() {  }  /**  * 判断cron表达式是否无效  *  * @param cron cron表达式  * @return 判定结果  */  public static Boolean isInvalidCron(String cron) {  return !isValidCron(cron);  }  /**  * 判断cron表达式是否有效  *  * @param cron cron表达式  * @return 判定结果  */  public static Boolean isValidCron(String cron) {  try {  new CronSequenceGenerator(cron);  return true;  } catch (IllegalArgumentException ex) {  return false;  }  }  /**  * 获取下次执行时间  *  * @param cron cron表达式  * @return 下次执行时间  */  public static LocalDateTime getNextExecTime(String cron) {// 因为CronSequenceGenerator.next接受的入参只有java.util.Date,而我又习惯使用LocalDateTime,因此加入此转换return new CronSequenceGenerator(cron).next(new Date()).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();  }  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/64504.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 1396. 设计地铁系统

1.题目基本信息 1.1.题目描述 地铁系统跟踪不同车站之间的乘客出行时间,并使用这一数据来计算从一站到另一站的平均时间。 实现 UndergroundSystem 类:void checkIn(int id, string stationName, int t)通行卡 ID 等于 id 的乘客,在时间 t ,从 stationName 站进入乘客一次只…

C# 开源浏览器性能提升,体验Chrome级速度

前言 使用 C# 和 CefSharp 开发的全功能网页浏览器。 项目介绍 SharpBrowser 是目前最快的开源 C# 网页浏览器! 采用了轻量级的 CEF 渲染器,在呈现网页时甚至比 Google Chrome 更快。 我们对比了所有可用的.NET 浏览器引擎,最终选择了高性能的 CefSharp。 SharpBrowser 使用…

springcloud的热点数据进行流控

简单理解就是,同样请求一个接口的入参,针对该参数对应是规定值的数据请求,进行控制,比如我一个接口的一个参数为id,如果id值为1002、1003的入参进行热点控制,别的id值不控制随意请求。 采用的是sentinel进行热点数据控制 设置如下这个热点设置,需要借助@SentinelResour…

《鸿蒙/Harmony | 开发日志》预览文件

APP 中常有需求就是点击文件打开预览。 鸿蒙中,可以借助访问的预览文件服务来实现。 测试下来,常见的文档类型txt, doc, excel, ppt,pdf, 图片,视频等都是默认可以打开的。遇到不能打开的,界面也会按钮是否使用其他 APP 来打开。支持的文件类型 官方文档列出的支持类型,实…

redis-配置文件解读

Redis配置文件解读 第一节 网络配置相关 bind绑定连接IP 默认情况bind=127.0.0.1只能接受本机的访问请求,不写的情况下,无限制接受任何ip地址的访问,生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉.如果开启了protected-mode,那么在没有设…

Spring-MVC

Spring-MVC 介绍 https://docs.spring.io/spring-framework/reference/web/webmvc.html Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring …

【泛微E9】查询部门的部门层级以及所有上级部门

效果图如下:field1:一级部门 field2:二级部门 field3:三级部门 field4:四级部门 field5:五级部门 field6:六级部门 创建视图,view_bmcjpath 视图定义如下: WITH RECURSIVE department_tree (id, DEPARTMENTMARK, supdepid, depth, path) AS ( -- 初始化查询(非递归部…

Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载

Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载 基于 ARM 的 Windows 10 请访问原文链接:https://sysin.org/blog/windows-10-arm/,查看最新版…