首页IT科技springboot dynamic druid(SpringBoot项目动态定时任务之 ScheduledTaskRegistrar(解决方案一))

springboot dynamic druid(SpringBoot项目动态定时任务之 ScheduledTaskRegistrar(解决方案一))

时间2025-06-18 03:22:17分类IT科技浏览4077
导读:前言 ​ 在做SpringBoot项目的过程中,有时客户会提出按照指定时间执行一次业务的需求。...

前言

​ 在做SpringBoot项目的过程中           ,有时客户会提出按照指定时间执行一次业务的需求            。

​ 如果客户需要改动业务的执行时间                 ,即动态地调整定时任务的执行时间      ,那么可以采用SpringBoot自带的ScheduledTaskRegistrar类作为解决方案来实现                 。

​ 在单一使用ScheduledTaskRegistrar类解决定时任务问题的时候         ,可能会达不到预期的动态调整定时任务的效果     。

​ 如果灵活配合使用对应的工具类(ThreadPoolTaskScheduler类)                 ,则可以方便地对动态调整定时任务进行管理         。

​ 本文会从问题出发         ,详细介绍ScheduledTaskRegistrar类是如何解决动态调整定时任务的思路      ,并给出关键的代码示例                 ,帮助大家快速地上手学习                  。

目录

一            、问题背景

在指定的某一时刻执行业务;

可以手动地更改执行时间        。

​ 在实际项目中            ,很少会有傻瓜式地去指定某一时间就触发某个业务的场景   ,执行业务的时间不是一成不变的                 ,而是动态地随着客户所指定的时间进行调整的      。

二                 、痛点所在

如果单一地使用SpringBoot自带的ScheduledTaskRegistrar去实现               ,那么可能会有以下问题:

只能按照指定的时间去执行,更改执行时间需要重启服务; 无法删除该定时任务              ,或者删除后无法再启动新的定时任务                  。 业务逻辑与触发器的代码耦合度太高                  ,无法将业务代码从ScheduledTaskRegistrar类中抽离出去           。 /** * @author Created by zhuzqc on 2023/1/30 15:28 */ @Slf4j @Component @EnableScheduling public class ScheduleTaskDemo implements SchedulingConfigurer { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { //Runnable线程注册任务 Runnable taskOne = () -> { //需要执行的业务逻辑   ,一般会在这里封装好 logger.info("----------业务执行结束----------"); }; //任务的触发时间           ,一般使用 cron 表达式 Trigger triggerOne = triggerContext -> { Date nextExecTime = null; try { // 此处指定 cron 表达式 String cron = "0 00 12 ? * *"; if (StringUtils.isBlank(cron)) { // 提示参数为空 logger.info("trigger定时器的 cron 参数为空!"); // 如果为空则赋默认值                 ,每天中午12点 cron = "0 00 12 ? * *"; } logger.info("---------->定时任务执行中<---------"); CronTrigger cronTrigger = new CronTrigger(cron); nextExecTime = cronTrigger.nextExecutionTime(triggerContext); } catch (Exception e) { e.printStackTrace(); log.info(e.getMessage()); } return nextExecTime; }; taskRegistrar.addTriggerTask(taskOne, triggerOne); } }

​ 上述代码只能实现在指定的时间去触发定时任务      ,无法对 cron 表达式进行更改         ,如果更改则需要重新启动服务                 ,非常地“傻瓜           ”   。

​ 而在实际的编码过程中         ,业务逻辑代码需要单独地剥离开(解耦)      ,如何做到业务逻辑代码和触发器代码都能访问到外部业务数据                 ,是设计过程中需要考虑到的关键                  。

三     、解决思路

//TODO:如果要在此处将业务逻辑和时间触发器进行捆绑            ,那么在这个实现类中无法获取到来自该类外部的业务数据; //TODO:要解决这样的问题   ,就要找到一个办法:既能将两者抽离                 ,又能实现灵活触发定时任务              。

​ 在这里介绍一个名为ThreadPoolTaskScheduler类               ,通过源码得知,该类实现了SchedulingTaskExecutor和TaskScheduler接口。

​ 该类中schedule(Runnable task, Trigger trigger)方法              ,通过分别传入线程任务(业务逻辑)Trigger触发器对象作为参数                  ,支持动态创建指定 cron 表达式的定时任务               。

​ 该方法源码如下:

@Override @Nullable public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) { ScheduledExecutorService executor = getScheduledExecutor(); try { ErrorHandler errorHandler = this.errorHandler; if (errorHandler == null) { errorHandler = TaskUtils.getDefaultErrorHandler(true); } return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule(); } catch (RejectedExecutionException ex) { throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex); } }

以下部分是对该方法的具体使用   ,核心思路如下 :

实例化ThreadPoolTaskScheduler类对象; 实例化ScheduledFuture类对象           ,用于初始化调用schedule()后的值                 。 将携带有Runnable和Trigger的ScheduledFuture类对象作为Map的value进行装配   。 根据Map的key对定时任务进行管理                 ,达到添加和删除的目的            。

四         、代码示例

代码示例分为两部分:

第一部分是关于ThreadPoolTaskScheduler类和schedule()方法的使用;

/** * @author @author Created by zhuzqc on 2023/1/30 15:39 * 任务线程池管理工具 */ public class TaskSchedulerUtil { private static final Logger logger = LoggerFactory.getLogger(TaskSchedulerUtil.class); /** * 线程调度工具对象      ,作为该类的成员变量 */ private static ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); /** *初始化 map 对象         ,装配 schedule 方法的返回对象为 value 值 */ private static Map<String, ScheduledFuture<?>> scheduledFutureMap = new HashMap<String, ScheduledFuture<?>>(); static { threadPoolTaskScheduler.initialize(); } /** * 将Runnable对象和Trigger对象作为参数传入该静态方法 * @param runnable * @param trigger * @param 定时任务id */ public static void put(Runnable runnable, Trigger trigger, String id) { // 将携带有Runnable和Trigger的ScheduledFuture类对象作为 Map 的 value 进行装配 ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(runnable, trigger); // 放入 Map 中作为 value scheduledFutureMap.put(id, scheduledFuture); logger.info("---添加定时任务--->" + id); } /** * 通过上述 put 方法的参数id(定时任务id)标识                 ,将定时任务移除出 map * @param id */ public static void delete(String id) { ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(id); // 条件判断 if (scheduledFuture != null && scheduledFuture.isCancelled()) { scheduledFuture.cancel(true); } scheduledFutureMap.remove(id); logger.info("---取消定时任务--->" + id); } }

第二部分是关于结合实际业务         ,引入实际业务数据的代码demo                 。

/** * @author Created by zhuzqc on 2023/1/30 15:58 */ @Slf4j @Component @EnableScheduling public class TaskScheduleDemo{ @Resource private AAAMapper aaaMapper; @Resource private BBBService bbbService; private Logger logger = LoggerFactory.getLogger(this.getClass()); // 引入外部的业务数据对象 public void putHiredTask(CCCEntity cccEntity){ //TODO: 将业务线程和定时触发器交由线程池工具管理:创建业务线程对象      ,并对属性赋初始化值(有参构造) TaskSchedulerUtil.put(new TaskThreadDemo(cccEntity,aaaMapper,bbbService), new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { //获取定时触发器                 ,这里可以获取页面的更新记录            ,实现定时间隔的动态调整 Date nextExecTime = TaskTransUtils.StringToDateTime(cccEntity.getSendTime()); //cron 表达式转换工具类 String cron = TaskTransUtils.getDateCronTime(nextExecTime); try { if (StringUtils.isBlank(cron)) { // 提示参数为空 logger.info("trackScheduler定时器的 cron 参数为空!"); // 如果为空则赋默认值   ,每天早上9:00 cron = "0 00 09 ? * *"; } logger.info("-------定时任务执行中:" + cron + "--------"); CronTrigger cronTrigger = new CronTrigger(cron); nextExecTime = cronTrigger.nextExecutionTime(triggerContext); } catch (Exception e) { e.printStackTrace(); log.info(e.getMessage()); } return nextExecTime; } },"该定时任务的id"); } }

五                  、文章小结

动态定时任务的总结如下:

单一使用ScheduledTaskRegistrar类                 ,无法达到预期动态调整定时任务的效果; 实际的开发场景中               ,需要业务逻辑代码和触发器代码都能访问到外部业务数据; 配合ThreadPoolTaskScheduler类和该类中的schedule()方法可以达到动态调整定时任务的效果     。

如果大家有遇到这样类似的问题,并且为此感到困惑时              ,希望以上文章的介绍可以帮助到大家         。

最后                  ,欢迎大家的指正和交流!

声明:本站所有文章   ,如无特殊说明或标注           ,均为本站原创发布                  。任何个人或组织                 ,在未征得本站同意时      ,禁止复制        、盗用      、采集                  、发布本站内容到任何网站           、书籍等各类媒体平台        。如若本站内容侵犯了原著者的合法权益         ,可联系我们进行处理      。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
bios里面的电源管理设置要怎么设置(apmd命令 – 进阶电源管理服务程序) vue代码大全(vue 代码调试神器)