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

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

时间2025-09-23 14:47:08分类IT科技浏览4901
导读:前言 ​ 在做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
买国外的服务器能上国外的网站(外国购买服务器租用攻略以及相关注意事项) 创建pin是什么意思啊电脑(pinia的基本创建和统一创建和解构失去响应式解决办法等知识点)