欢迎关注方志朋的博客,回复”666“获面试宝典

一、功能说明

SpringBoot的定时任务的加强工具,实现对SpringBoot原生的定时任务进行动态管理,完全兼容原生@Scheduled注解,无需对原本的定时任务进行修改

二、快速使用

具体的功能已经封装成SpringBoot-starter即插即用

<dependency><groupId>com.github.guoyixing</groupId><artifactId>spring-boot-starter-super-scheduled</artifactId><version>0.3.1</version>
</dependency>

使用方法和源码:

码云:https://gitee.com/qiaodaimadewangcai/super-scheduled

github:https://github.com/guoyixing/super-scheduled

三、实现原理

1、动态管理实现

(1) 配置管理介绍

@Component("superScheduledConfig")
public class SuperScheduledConfig {/*** 执行定时任务的线程池*/private ThreadPoolTaskScheduler taskScheduler;/*** 定时任务名称与定时任务回调钩子  的关联关系容器*/private Map<String, ScheduledFuture> nameToScheduledFuture = new ConcurrentHashMap<>();/*** 定时任务名称与定时任务需要执行的逻辑  的关联关系容器*/private Map<String, Runnable> nameToRunnable = new ConcurrentHashMap<>();/*** 定时任务名称与定时任务的源信息  的关联关系容器*/private Map<String, ScheduledSource> nameToScheduledSource = new ConcurrentHashMap<>();/* 普通的get/sets省略 */
}

(2) 使用后处理器拦截SpringBoot原本的定时任务

  • 实现ApplicationContextAware接口拿到SpringBoot的上下文

  • 实现BeanPostProcessor接口,将这个类标记为后处理器,后处理器会在每个bean实例化之后执行

  • 使用@DependsOn注解强制依赖SuperScheduledConfig类,让SpringBoot实例化SuperScheduledPostProcessor类之前先实例化SuperScheduledConfig类

  • 主要实现逻辑在postProcessAfterInitialization()方法中

@DependsOn({"superScheduledConfig"})
@Component
@Order
public class SuperScheduledPostProcessor implements BeanPostProcessor, ApplicationContextAware {protected final Log logger = LogFactory.getLog(getClass());private ApplicationContext applicationContext;/*** 实例化bean之前的操作* @param bean bean实例* @param beanName bean的Name*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}/*** 实例化bean之后的操作* @param bean bean实例* @param beanName bean的Name*/@Overridepublic Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {//1.获取配置管理器SuperScheduledConfig superScheduledConfig = applicationContext.getBean(SuperScheduledConfig.class);//2.获取当前实例化完成的bean的所有方法Method[] methods = bean.getClass().getDeclaredMethods();//循环处理对每个方法逐一处理if (methods.length > 0) {for (Method method : methods) {//3.尝试在该方法上获取@Scheduled注解(SpringBoot的定时任务注解)Scheduled annotation = method.getAnnotation(Scheduled.class);//如果无法获取到@Scheduled注解,就跳过这个方法if (annotation == null) {continue;}//4.创建定时任务的源属性//创建定时任务的源属性(用来记录定时任务的配置,初始化的时候记录的是注解上原本的属性)ScheduledSource scheduledSource = new ScheduledSource(annotation, method, bean);//对注解上获取到源属性中的属性进行检测if (!scheduledSource.check()) {throw new SuperScheduledException("在" + beanName + "Bean中" + method.getName() + "方法的注解参数错误");}//生成定时任务的名称(id),使用beanName+“.”+方法名String name = beanName + "." + method.getName();//将以key-value的形式,将源数据存入配置管理器中,key:定时任务的名称 value:源数据superScheduledConfig.addScheduledSource(name, scheduledSource);try {//5.将原本SpringBoot的定时任务取消掉clearOriginalScheduled(annotation);} catch (Exception e) {throw new SuperScheduledException("在关闭原始方法" + beanName + method.getName() + "时出现错误");}}}//最后bean保持原有返回return bean;}/*** 修改注解原先的属性* @param annotation 注解实例对象* @throws Exception*/private void clearOriginalScheduled(Scheduled annotation) throws Exception {changeAnnotationValue(annotation, "cron", Scheduled.CRON_DISABLED);changeAnnotationValue(annotation, "fixedDelay", -1L);changeAnnotationValue(annotation, "fixedDelayString", "");changeAnnotationValue(annotation, "fixedRate", -1L);changeAnnotationValue(annotation, "fixedRateString", "");changeAnnotationValue(annotation, "initialDelay", -1L);changeAnnotationValue(annotation, "initialDelayString", "");}/*** 获取SpringBoot的上下文* @param applicationContext SpringBoot的上下文*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

(3) 使用ApplicationRunner初始化自定义的定时任务运行器

  • 实现ApplicationContextAware接口拿到SpringBoot的上下文

  • 使用@DependsOn注解强制依赖threadPoolTaskScheduler类

  • 实现ApplicationRunner接口,在所有bean初始化结束之后,运行自定义逻辑

  • 主要实现逻辑在run()方法中

@DependsOn("threadPoolTaskScheduler")
@Component
public class SuperScheduledApplicationRunner implements ApplicationRunner, ApplicationContextAware {protected final Log logger = LogFactory.getLog(getClass());private DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");private ApplicationContext applicationContext;/*** 定时任务配置管理器*/@Autowiredprivate SuperScheduledConfig superScheduledConfig;/*** 定时任务执行线程*/@Autowiredprivate ThreadPoolTaskScheduler threadPoolTaskScheduler;@Overridepublic void run(ApplicationArguments args) {//1.定时任务配置管理器中缓存  定时任务执行线程superScheduledConfig.setTaskScheduler(threadPoolTaskScheduler);//2.获取所有定时任务源数据Map<String, ScheduledSource> nameToScheduledSource = superScheduledConfig.getNameToScheduledSource();//逐一处理定时任务for (String name : nameToScheduledSource.keySet()) {//3.获取定时任务源数据ScheduledSource scheduledSource = nameToScheduledSource.get(name);//4.获取所有增强类String[] baseStrengthenBeanNames = applicationContext.getBeanNamesForType(BaseStrengthen.class);//5.创建执行控制器SuperScheduledRunnable runnable = new SuperScheduledRunnable();//配置执行控制器runnable.setMethod(scheduledSource.getMethod());runnable.setBean(scheduledSource.getBean());//6.逐一处理增强类(增强器实现原理后面具体分析)List<Point> points = new ArrayList<>(baseStrengthenBeanNames.length);for (String baseStrengthenBeanName : baseStrengthenBeanNames) {//7.将增强器代理成pointObject baseStrengthenBean = applicationContext.getBean(baseStrengthenBeanName);//创建代理Point proxy = ProxyUtils.getInstance(Point.class, new RunnableBaseInterceptor(baseStrengthenBean, runnable));proxy.setSuperScheduledName(name);//8.所有的points连成起来points.add(proxy);}//将point形成调用链runnable.setChain(new Chain(points));//将执行逻辑封装并缓存到定时任务配置管理器中superScheduledConfig.addRunnable(name, runnable::invoke);try {//8.启动定时任务ScheduledFuture<?> schedule = ScheduledFutureFactory.create(threadPoolTaskScheduler, scheduledSource, runnable::invoke);//将线程回调钩子存到任务配置管理器中superScheduledConfig.addScheduledFuture(name, schedule);logger.info(df.format(LocalDateTime.now()) + "任务" + name + "已经启动...");} catch (Exception e) {throw new SuperScheduledException("任务" + name + "启动失败,错误信息:" + e.getLocalizedMessage());}}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

(4) 进行动态管理

@Component
public class SuperScheduledManager {protected final Log logger = LogFactory.getLog(getClass());private DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@Autowiredprivate SuperScheduledConfig superScheduledConfig;/*** 修改Scheduled的执行周期** @param name scheduled的名称* @param cron cron表达式*/public void setScheduledCron(String name, String cron) {//终止原先的任务cancelScheduled(name);//创建新的任务ScheduledSource scheduledSource = superScheduledConfig.getScheduledSource(name);scheduledSource.clear();scheduledSource.setCron(cron);addScheduled(name, scheduledSource);}/*** 修改Scheduled的fixedDelay** @param name       scheduled的名称* @param fixedDelay 上一次执行完毕时间点之后多长时间再执行*/public void setScheduledFixedDelay(String name, Long fixedDelay) {//终止原先的任务cancelScheduled(name);//创建新的任务ScheduledSource scheduledSource = superScheduledConfig.getScheduledSource(name);scheduledSource.clear();scheduledSource.setFixedDelay(fixedDelay);addScheduled(name, scheduledSource);}/*** 修改Scheduled的fixedRate** @param name      scheduled的名称* @param fixedRate 上一次开始执行之后多长时间再执行*/public void setScheduledFixedRate(String name, Long fixedRate) {//终止原先的任务cancelScheduled(name);//创建新的任务ScheduledSource scheduledSource = superScheduledConfig.getScheduledSource(name);scheduledSource.clear();scheduledSource.setFixedRate(fixedRate);addScheduled(name, scheduledSource);}/*** 查询所有启动的Scheduled*/public List<String> getRunScheduledName() {Set<String> names = superScheduledConfig.getNameToScheduledFuture().keySet();return new ArrayList<>(names);}/*** 查询所有的Scheduled*/public List<String> getAllSuperScheduledName() {Set<String> names = superScheduledConfig.getNameToRunnable().keySet();return new ArrayList<>(names);}/*** 终止Scheduled** @param name scheduled的名称*/public void cancelScheduled(String name) {ScheduledFuture scheduledFuture = superScheduledConfig.getScheduledFuture(name);scheduledFuture.cancel(true);superScheduledConfig.removeScheduledFuture(name);logger.info(df.format(LocalDateTime.now()) + "任务" + name + "已经终止...");}/*** 启动Scheduled** @param name            scheduled的名称* @param scheduledSource 定时任务的源信息*/public void addScheduled(String name, ScheduledSource scheduledSource) {if (getRunScheduledName().contains(name)) {throw new SuperScheduledException("定时任务" + name + "已经被启动过了");}if (!scheduledSource.check()) {throw new SuperScheduledException("定时任务" + name + "源数据内容错误");}scheduledSource.refreshType();Runnable runnable = superScheduledConfig.getRunnable(name);ThreadPoolTaskScheduler taskScheduler = superScheduledConfig.getTaskScheduler();ScheduledFuture<?> schedule = ScheduledFutureFactory.create(taskScheduler, scheduledSource, runnable);logger.info(df.format(LocalDateTime.now()) + "任务" + name + "已经启动...");superScheduledConfig.addScheduledSource(name, scheduledSource);superScheduledConfig.addScheduledFuture(name, schedule);}/*** 以cron类型启动Scheduled** @param name scheduled的名称* @param cron cron表达式*/public void addCronScheduled(String name, String cron) {ScheduledSource scheduledSource = new ScheduledSource();scheduledSource.setCron(cron);addScheduled(name, scheduledSource);}/*** 以fixedDelay类型启动Scheduled** @param name         scheduled的名称* @param fixedDelay   上一次执行完毕时间点之后多长时间再执行* @param initialDelay 第一次执行的延迟时间*/public void addFixedDelayScheduled(String name, Long fixedDelay, Long... initialDelay) {ScheduledSource scheduledSource = new ScheduledSource();scheduledSource.setFixedDelay(fixedDelay);if (initialDelay != null && initialDelay.length == 1) {scheduledSource.setInitialDelay(initialDelay[0]);} else if (initialDelay != null && initialDelay.length > 1) {throw new SuperScheduledException("第一次执行的延迟时间只能传入一个参数");}addScheduled(name, scheduledSource);}/*** 以fixedRate类型启动Scheduled** @param name         scheduled的名称* @param fixedRate    上一次开始执行之后多长时间再执行* @param initialDelay 第一次执行的延迟时间*/public void addFixedRateScheduled(String name, Long fixedRate, Long... initialDelay) {ScheduledSource scheduledSource = new ScheduledSource();scheduledSource.setFixedRate(fixedRate);if (initialDelay != null && initialDelay.length == 1) {scheduledSource.setInitialDelay(initialDelay[0]);} else if (initialDelay != null && initialDelay.length > 1) {throw new SuperScheduledException("第一次执行的延迟时间只能传入一个参数");}addScheduled(name, scheduledSource);}/*** 手动执行一次任务** @param name scheduled的名称*/public void runScheduled(String name) {Runnable runnable = superScheduledConfig.getRunnable(name);runnable.run();}
}
2、增强接口实现

增强器实现的整体思路与SpringAop的思路一致,实现没有Aop复杂

(1) 增强接口

@Order(Ordered.HIGHEST_PRECEDENCE)
public interface BaseStrengthen {/*** 前置强化方法** @param bean   bean实例(或者是被代理的bean)* @param method 执行的方法对象* @param args   方法参数*/void before(Object bean, Method method, Object[] args);/*** 后置强化方法* 出现异常不会执行* 如果未出现异常,在afterFinally方法之后执行** @param bean   bean实例(或者是被代理的bean)* @param method 执行的方法对象* @param args   方法参数*/void after(Object bean, Method method, Object[] args);/*** 异常强化方法** @param bean   bean实例(或者是被代理的bean)* @param method 执行的方法对象* @param args   方法参数*/void exception(Object bean, Method method, Object[] args);/*** Finally强化方法,出现异常也会执行** @param bean   bean实例(或者是被代理的bean)* @param method 执行的方法对象* @param args   方法参数*/void afterFinally(Object bean, Method method, Object[] args);
}

(2) 代理抽象类

public abstract class Point {/*** 定时任务名*/private String superScheduledName;/*** 抽象的执行方法,使用代理实现* @param runnable 定时任务执行器*/public abstract Object invoke(SuperScheduledRunnable runnable);/* 普通的get/sets省略 */
}

(3) 调用链类

public class Chain {private List<Point> list;private int index = -1;/*** 索引自增1*/public int incIndex() {return ++index;}/*** 索引还原*/public void resetIndex() {this.index = -1;}
}

(4) cglib动态代理实现

使用cglib代理增强器,将增强器全部代理成调用链节点Point

public class RunnableBaseInterceptor implements MethodInterceptor {/*** 定时任务执行器*/private SuperScheduledRunnable runnable;/*** 定时任务增强类*/private BaseStrengthen strengthen;@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object result;//如果执行的是invoke()方法if ("invoke".equals(method.getName())) {//前置强化方法strengthen.before(obj, method, args);try {//调用执行器中的invoke()方法result = runnable.invoke();} catch (Exception e) {//异常强化方法strengthen.exception(obj, method, args);throw new SuperScheduledException(strengthen.getClass() + "中强化执行时发生错误", e);} finally {//Finally强化方法,出现异常也会执行strengthen.afterFinally(obj, method, args);}//后置强化方法strengthen.after(obj, method, args);} else {//直接执行方法result = methodProxy.invokeSuper(obj, args);}return result;}public RunnableBaseInterceptor(Object object, SuperScheduledRunnable runnable) {this.runnable = runnable;if (BaseStrengthen.class.isAssignableFrom(object.getClass())) {this.strengthen = (BaseStrengthen) object;} else {throw new SuperScheduledException(object.getClass() + "对象不是BaseStrengthen类型");}}public RunnableBaseInterceptor() {}
}

(5) 定时任务执行器实现

public class SuperScheduledRunnable {/*** 原始的方法*/private Method method;/*** 方法所在的bean*/private Object bean;/*** 增强器的调用链*/private Chain chain;public Object invoke() {Object result;//索引自增1if (chain.incIndex() == chain.getList().size()) {//调用链中的增强方法已经全部执行结束try {//调用链索引初始化chain.resetIndex();//增强器全部执行完毕,执行原本的方法result = method.invoke(bean);} catch (IllegalAccessException | InvocationTargetException e) {throw new SuperScheduledException(e.getLocalizedMessage());}} else {//获取被代理后的方法增强器Point point = chain.getList().get(chain.getIndex());//执行增强器代理//增强器代理中,会回调方法执行器,形成调用链,逐一运行调用链中的增强器result = point.invoke(this);}return result;}/* 普通的get/sets省略 */
}

(6) 增强器代理逻辑

com.gyx.superscheduled.core.SuperScheduledApplicationRunner类中的代码片段

//创建执行控制器
SuperScheduledRunnable runnable = new SuperScheduledRunnable();
runnable.setMethod(scheduledSource.getMethod());
runnable.setBean(scheduledSource.getBean());
//用来存放 增强器的代理对象
List<Point> points = new ArrayList<>(baseStrengthenBeanNames.length);
//循环所有的增强器的beanName
for (String baseStrengthenBeanName : baseStrengthenBeanNames) {//获取增强器的bean对象Object baseStrengthenBean = applicationContext.getBean(baseStrengthenBeanName);//将增强器代理成Point节点Point proxy = ProxyUtils.getInstance(Point.class, new RunnableBaseInterceptor(baseStrengthenBean, runnable));proxy.setSuperScheduledName(name);//增强器的代理对象缓存到list中points.add(proxy);
}
//将增强器代理实例的集合生成调用链
//执行控制器中设置调用链
runnable.setChain(new Chain(points));

来源:blog.csdn.net/qq_34886352/article/details/106494637

热门内容:
  • IntelliJ IDEA详细配置图解,挖掘更多的功能!

  • 阿里二面:main 方法可以继承吗?

  • 离开互联网上岸1年后,我后悔了!重回大厂内卷

  • 逃过大厂“开猿节流”,斩获12家offer,最牛笔记曝光!

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

SpringBoot 定时任务动态管理通用解决方案相关推荐

  1. 定时任务动态管理-Scheduled

    文章目录 前言 一.架构流程图 二.代码实现流程 1.引入库 2.代码流程 前言 定时任务动态管理分为两种方式: 方式一:Web前台配置Trigger触发器(关联Cron).ThreadPoolTas ...

  2. SpringBoot+Quartz动态管理定时任务

    前置理论: 1.小顶堆(适合任务少的,因为向下调整耗费性能) 堆:是一完全二叉树(除了最后一层节点其他层都达到最大节点数,且最后一层都靠左排列):堆中某个节点的值总不大于或不小于其父节点. 定时任务是 ...

  3. springboot 定时任务动态启动和停止

    这个在网上都有解决方案了,但是网上的给的解决方案夹杂了本身的业务,其实动态启动定时任务只需要关注系统启动时的动态启停,至于要不要入库,取决于自身业务情况,与解决这个问题没啥关系:ok,我来梳理下我这边 ...

  4. quartz 动态添加job_SpringBoot+Quartz实现动态管理定时任务

    前言: 最近在做Java开发对接微信公众平台之类的工作,在开发过程中遇到一个需求是定时任务这块的,但是普通的定时任务还远远不能满足:最后要实现的效果是每个任务都是不同的定时时间来执行而且是在前台页面上 ...

  5. ThreadPoolTaskScheduler实现动态管理定时任务

    最近,有个项目有需要用到定时任务,所以做了一个动态管理定时任务的模块.本文将从项目背景.需求.选型.思路.具体实现等方面展开介绍. 背景:有个支付类的项目,中间会产生一些中间态的订单,需要有个定时任务 ...

  6. Springboot调度任务:动态管理

    前言 现在智能手表.手环,相信很多人都使用过,其中有一个功能,就是会有各种的提醒,如喝水提醒.运动提醒.远眺提醒,本质上根据用户的设置,间隔一定时间执行一个调度任务,提醒用户做某件事情.这篇文章将以这 ...

  7. springBoot+security+mybatis 实现用户权限的数据库动态管理

    [b][size=large]一.Spring Security 应用的概述[/size][/b] [size=medium] 鉴于目前微服务的兴起,Spring周边方案的普及,以及 Spring S ...

  8. SpringBoot定时任务升级篇(动态添加修改删除定时任务)

    (1)思路说明: (a)首先这里我们需要重新认识一个类ThreadPoolTaskScheduler:线程池任务调度类,能够开启线程池进行任务调度. (b)ThreadPoolTaskSchedule ...

  9. springboot定时任务未登录情况下获取用户信息报错解决方案

    解决org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling ...

最新文章

  1. 对操作系统安全构成威胁的问题
  2. go防止多次递交表单
  3. Android完全退出程序、线程
  4. Django模板语言(译)
  5. qt.targets(66,3):元素<Import>中的“Project”特性的值无效
  6. 有空研究这篇http://blog.csdn.net/studyvcmfc/article/details/7720258 研究后写篇记录
  7. C++指针、this指针、静态成员
  8. access中判断回文的代码_前端也来点算法(TypeScript版) | 2 - 回文数和回文链表
  9. 制作好的app需要服务器吗,在直播app制作过程中,服务器是如何配置的?
  10. BI工具选型需考虑哪些问题
  11. LoadRunner 录制常见错误解决方法
  12. 物件导向比面向对象更准确
  13. 15针VGA公头焊接示意图
  14. 一张A4纸打印多张财务凭证(分栏报表)
  15. 张北川:命名数据网络(NDN)
  16. 安卓下快速搜索文件实现历程{NDK}
  17. 如何将多个PDF文件合并为一个PDF文件?PDF文件合并教程
  18. 2020-10-14
  19. 如何利用PS中的钢笔抠图
  20. 为了让自己变得更优秀,我喜欢上了这2位B站up主

热门文章

  1. 记录一次爬取某昵称网站的爬虫
  2. [PHP] JQuery+Layer实现添加删除自定义标签代码
  3. ABP中的Filter(下)
  4. 前端基础之JQuery
  5. Hadoop的存储架构介绍
  6. linux下QT Creator常见错误及解决办法
  7. ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务解决
  8. html 11 内联(行内)
  9. Datawhale组队学习周报(第040周)
  10. 如何在Matlab中获取函数参数的数目?