范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Spring定时任务玩出花

  1. 项目概览
  我们首先来大概看下这个项目:
  这里和定时任务相关的配置主要在 config 包里边,其他的都是业务类代码,换句话说其他的都是常规的 CURD,所以我这里主要和小伙伴们介绍 config 中的代码。  2. 整体思路
  我先来说说这个项目的整体思路,这样方便大家理解下面的内容。
  在这个项目中,每一个定时任务都由一个线程去处理,负责处理每一个定时任务的线程类是 SchedulingRunnable,所有的线程都跑在一个线程池中,这个线程池是 ThreadPoolTaskScheduler,这是一个专为定时任务设计的线程池(支持 Cron 表达式),它的底层其实就是大家所熟知的 ScheduledThreadPoolExecutor。当有一个新的定时任务需要执行时,创建一个 SchedulingRunnable 线程,然后连同 Cron 表达式一起扔到 ThreadPoolTaskScheduler 池子里去执行就行了。  3. 配置分析
  几个配置类我们逐一来分析。  3.1 SpringContextUtils
  首先我们提供了一个 SpringContextUtils 工具类,这个工具类实现了 ApplicationContextAware 接口,通过这个工具类,我们可以从 Spring 容器中查询一个 Bean 或者判断 Spring 容器中是否存在某一个 Bean,工具类的代码如下(我主要列出来了有哪些方法,具体实现大家可以参考:https://github.com/lenve/scheduling):  @Component public class SpringContextUtils implements ApplicationContextAware {      private static ApplicationContext applicationContext;      @Override     public void setApplicationContext(ApplicationContext applicationContext)             throws BeansException {         SpringContextUtils.applicationContext = applicationContext;     }      public static Object getBean(String name) {     }      public static  T getBean(Class requiredType) {     }      public static  T getBean(String name, Class requiredType) {     }      public static boolean containsBean(String name) {     }      public static boolean isSingleton(String name) {     }      public static Class<? extends Object> getType(String name) {     } } 3.2 SchedulingRunnable
  将来每一个定时任务执行的时候,我们都开启一个新的线程去执行这个定时任务,SchedulingRunnable 就是关于这个线程的配置,我们来看下:  public class SchedulingRunnable implements Runnable {      private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);      private String beanName;      private String methodName;      private String params;      private Object targetBean;      private Method method;      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;         init();     }      private void init() {         try {             targetBean = SpringContextUtils.getBean(beanName);              if (StringUtils.hasText(params)) {                 method = targetBean.getClass().getDeclaredMethod(methodName, String.class);             } else {                 method = targetBean.getClass().getDeclaredMethod(methodName);             }              ReflectionUtils.makeAccessible(method);         } catch (NoSuchMethodException e) {             e.printStackTrace();         }     }      @Override     public void run() {         logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);         long startTime = System.currentTimeMillis();          try {             if (StringUtils.hasText(params)) {                 method.invoke(targetBean, params);             } else {                 method.invoke(targetBean);             }         } 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);     } }
  SchedulingRunnable 实现了 Runnable 接口,这里的实现逻辑也比较简单,我们一起来看下:  首先声明了 beanName、methodName 以及 params 分别作为定时任务执行的 Bean 的 bean 名称、方法名称以及方法参数。不知道小伙伴们是否记得我们上篇文章中介绍的该系统的用法,在添加一个定时任务时,我们需要传入相应的 beanName、methodName 以及 params 参数,传入后就来到这里了。另外还有 targetBean 和 method 分别表示 beanName 对应的对象以及 methodName 对应的对象,其中 targetBean 通过 beanName 从 Spring 容器中查找,method 则通过 methodName 从 targetBean 中查找。  在 run 方法中,通过反射去调用 method 方法,这也是定时任务执行时候的具体逻辑。  另外,这里重写了 equals 和 hashCode 方法,这两个方法主要是比较了 beanName、methodName 以及 params 三个属性,换言之,如果这三个属性相同,则认为这是同一个对象(这三个属性相同表示这是同一个定时任务)。  3.3 SchedulingConfig@Configuration public class SchedulingConfig {     @Bean     public TaskScheduler taskScheduler() {         ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();         taskScheduler.setPoolSize(4);         taskScheduler.setRemoveOnCancelPolicy(true);         taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");         return taskScheduler;     } }
  这里主要是配置一下 ThreadPoolTaskScheduler,这个可以很方便的对重复执行的任务进行调度管理,相比于通过 Java 自带的周期性任务线程池ScheduleThreadPoolExecutor,ThreadPoolTaskScheduler 对象支持根据 Cron 表达式创建周期性任务。
  既然是线程池,必然就有线程数量等问题,它的核心线程池大小就是我们配置的 poolSize 属性,最大线程池大小是  Integer.MAX_VALUE ,keepAliveTime  为 0 ,这里用到的队列是 DelayedWorkQueue  ,这个队列有一个属性 private final DelayQueue dq = new DelayQueue();  对这个队列的操作实际是是对这个 DelayQueue 的操作,这个队列大小是 Integer.MAX_VALUE,所以线程数量肯定是够用了。
  其他配置就没啥好说的。  3.4 ScheduledTask
  ScheduledTask 是 ScheduledFuture 的包装类,这个包装类中主要多了一个 future 属性,这个 future 属性表示 TaskScheduler 定时任务线程池的执行结果:  public final class ScheduledTask {     volatile ScheduledFuture<?> future;     public void cancel() {         ScheduledFuture<?> future = this.future;         if (future != null) {             future.cancel(true);         }     } } 3.5 CronTaskRegistrar
  核心的方法都在这个里边。  @Component public class CronTaskRegistrar implements DisposableBean {      private final Map scheduledTasks = new ConcurrentHashMap<>(16);      @Autowired     private TaskScheduler taskScheduler;      public TaskScheduler getScheduler() {         return this.taskScheduler;     }      public void addCronTask(Runnable task, String cronExpression) {         addCronTask(new CronTask(task, cronExpression));     }      public void addCronTask(CronTask cronTask) {         if (cronTask != null) {             Runnable task = cronTask.getRunnable();             if (this.scheduledTasks.containsKey(task)) {                 removeCronTask(task);             }              this.scheduledTasks.put(task, scheduleCronTask(cronTask));         }     }      public void removeCronTask(Runnable task) {         ScheduledTask scheduledTask = this.scheduledTasks.remove(task);         if (scheduledTask != null)             scheduledTask.cancel();     }      public ScheduledTask scheduleCronTask(CronTask cronTask) {         ScheduledTask scheduledTask = new ScheduledTask();         scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());          return scheduledTask;     }       @Override     public void destroy() {         for (ScheduledTask task : this.scheduledTasks.values()) {             task.cancel();         }          this.scheduledTasks.clear();     } }
  稍微说下这个类:  首先这个类实现了 DisposableBean 接口,实现这个接口就重写了 destroy 方法,以便在 Bean 销毁的时候,清除所有的定时任务。  addCronTask(Runnable, String) 方法用来添加一个定时任务,传两个参数,第一个是 Runnable,也就是我们前面所说的定时任务,第二个则是一个 Cron 表达式。  addCronTask(CronTask) 方法也用来添加定时任务,添加之前先判断这个定时任务是否已经存在,如果已经存在,就先移除。然后将定时任务存入 scheduledTasks 中,存储的时候,key 就是那个 Runnable 对象,value 则是一个 ScheduledTask 对象。  ScheduledTask 对象从 scheduleCronTask 方法中获取, 这也是整个系统最最核心的一段代码 ,调用 taskScheduler 对象把定时任务添加进去。  removeCronTask 方法用来移除一个定时任务,移除分为两部分:1. 从 scheduledTasks 集合中找到定时任务并移除;2. 取消定时任务的执行。  最后的 destroy 方法就是一个常规方法,该移除移除,该清空清空。  3.6 InitTask
  这是一个处理数据库中已有定时任务的类。当系统启动时,首先从数据库中读取需要定时执行的任务,然后挨个加入定时任务执行器中:  @Component public class InitTask implements CommandLineRunner {     @Autowired     CronTaskRegistrar cronTaskRegistrar;     @Autowired     SysJobService sysJobService;      @Override     public void run(String... args) throws Exception {         List list = sysJobService.getJobsByStatus(1);         for (SysJob sysJob : list) {             cronTaskRegistrar.addCronTask(new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams()), sysJob.getCronExpression());         }     } } 查询所有状态为 1 的定时任务。  遍历第一步查询出来的集合,添加定时任务。
  好啦,这就是整个项目最最核心的配置了,其他的代码都是一些业务层面的代码,乏善可陈,我就不啰嗦啦。  4. 定时任务怎么配
  有的小伙伴可能还不知道定时任务怎么配置,我这里稍微说两句。
  项目中提供了如下一个测试类:  @Component("schedulingTaskDemo") public class SchedulingTaskDemo {     public void taskWithParams(String params) {         System.out.println("执行有参示例任务:" + params);     }      public void taskNoParams() {         System.out.println("执行无参示例任务");     } }
  这是提前写好的,需要的时候我们配置的定时任务就是这里相关的参数,如下图:
  Bean 名称、方法名称都和测试案例中的 Bean 一一对应。  5. 小结
  好啦,是不是很 Easy?小伙伴们赶紧去尝试下吧!
  项目地址:  GitHub:https://github.com/lenve/scheduling  Gitee:https://gitee.com/lenve/scheduling  原文链接:https://mp.weixin.qq.com/s/skZ7uU7q1iH9QrV2EKiGOg
  原作者:江南一点雨

格罗夫的氢能新版图中极汽车上海车展首发中极氢能汽车品牌发布,天枢和天玑两款新车全球首发,国内氢能汽车领创者格罗夫正式进入商用车领域,并致力于引领行业驶入氢能汽车时代。全球首发的两款新车,都配备了最新的智能网联系统,在安520吉利真情告白没有红包,不算表白我们在一起,这很吉利!520表白日,13时14分,谐音一生一世的吉利时刻,吉利阿甘淦家阅向全新上线的新版吉利汽车APP注册用户派发520个1314积分红包,真情告白全球首个汽车用户爱驰合创签约好生活,共创汽车新消费时代拼流量的背后是在争抢用户的关注度。好生活的出现,为造车新势力带来了福音。4月26日,好生活在上海举办2021好生活合伙人年度盛典。合创爱驰等汽车品牌,率先与好生活签约,协同房产网红人民的大众,中国的吉利,比亚迪得自在豪华车不再是唯一的焦点,这是2021年上海车展最大的变化。随着特斯拉维权事件的持续发酵,豪华品牌的傲慢被进一步放大,今年上海车展也史无前例地天天上热搜,汽车消费真正进入了全民时代。销量吉利4月销量破10万,海外出口几近倍增5月10日,吉利汽车(HK。0175)公布2021年4月销量数据4月销量100331辆14月总销量433907辆,同比增长39。其中,全球新高端品牌领克汽车4月销量为19021辆,跑分惊呆!ThinkPadX1Carbon2020到手24小时测评作为万元商务机,绕不过去的一个型号正是ThinkPadX1Carbon,2020年又迎来了一次新的升级。新款在6月1号正式上市,叔第一时间从电商渠道下单购买,刚刚到手24个小时为大燕昭文十四五河北大健康产业有哪些发展重点燕昭文,河北大健康产业研究院院长十四五时期,我省大健康产业概括为3个4,即4大金刚4个童子4根台柱子,这构成了我省大健康产业体系的主要框架。4大金刚,是指大健康领域的4个主导产业4显示屏色域高达94新战神GX8GX10CP7S1震撼登场科技的高速发展给人们带来越来越精致的生活体验,生活用品的分类也越来越精确。电脑从台式机发展到可随身携带的笔记本,按用户需求可细分成商务本游戏本,两种本各有千秋。随着近几年电竞行业的完美避坑HiRes认证魅族HD60头戴式耳机25小时续航只要499在10月23日的魅族16T新品发布会上,魅族发布了多款产品,成功被魅族HD60种草了,拿到魅族HD60先来一片图赏开箱馋你一下外包装上那个HiRes认证标识很显眼啊打开外包装魅族H三河市生物医药与大健康产业集群成链河北县域特色产业集群样本80文河北资本研究会课题组追求健康已成为21世纪的大趋势,生物医药与大健康产业也由此迈入高质量发展新时代。在这一大背景下,近年来,三河市抢抓历史机遇,充分利荣耀手环6开启手环全面屏时代,打造健康生活与科学运动新体验自从手机代入全面屏这个概念以来,已经有越来越多的厂商将自己的产品打造成全面屏,但是之前全面屏并没有手环这类相对低端产品的什么事儿,直到荣耀手环6的发布。荣耀手环6采用1。47英寸臻
vivo执行副总裁胡柏山X70将首搭自研V1影像芯片8月27日,vivo在深圳召开科技创新沟通会,vivo执行副总裁胡柏山在会上宣布,自研影像芯片V1将在下个月的X70系列手机上首发。这是vivo布局芯片以来正式落地的第一款产品,满iPhone13系列支持卫星通信没蜂窝信号照样用PChome手机频道资讯报道据分析师郭明池最新消息称,苹果iPhone13系列将添加地球低轨道(LEO)卫星通信连接功能,能够在没有蜂窝网络覆盖的地区拨打电话发送短信和获得互联网服iPhone13发售时间曝光高通和中兴完成5G毫米波测试01hriPhone13发售时间渲染图曝光近日,追踪苹果的知名消息人士琼普罗瑟(JonProsser)透露,iPhone13系列将于9月14日发布,并将于9月24日在全球范围内开售微信又悄悄更新了,这7个强大又良心的功能,可惜知道的人不多微信作为国内用户量最多的社交软件,总是喜欢悄悄地更新功能,不知道更新的这些功能你发现没?建议尽快知道,因为真的很有用。01边写边译这个功能可以用于国际交流,主要是可以实时翻译提前设隐私无忧,智慧贴心荣耀MagicUI5。0系统体验8月12日荣耀发布的Magic3系列,作为荣耀独立后的首款高端旗舰,不仅在设计风格上焕然一新,带来了强悍的性能表现,在系统方面也首发搭载了MagicUI5。0。今天我们就来看看新系ORICO奥睿科指纹加密移动固态硬盘体验一键解锁隐私无忧虽然说现在是互联网的时代,万物皆可放在云端,但对于很多特殊工作需要的人来说,将数据存在云端是多么愚蠢的行为,我们先不说网速的上传下载有多么慢,但隐私泄漏倒是时常会发生的,当涉及到知追剧打游戏的神器,NXTWEARG智能眼镜带你享受视觉盛宴TCL这家国内老牌的电视厂商,一直以来都是安安稳稳的制造产品,因此也一直没有掀起什么大的水花,但是在最近推出的一款新品却引起了大家广泛的关注,它就是一款智能眼镜NXTWEARG。这诺基亚很大胆,6000mAh大电池,外加16512GB大存储,霸气十足诺基亚可以算是手机行业中的老江湖,大家对诺基亚都耳熟能详,即便没有用过也听说过。曾经诺基亚也像三星苹果华为小米一样火遍全球,但自智能手机兴起之后,诺基亚就跌入了低谷,一蹶不振。自从超性价比水桶机?红米Note10Pro手机深度拆解今年的5月26日,Redmi红米召开Note10系列新品发布会,发布了RedmiNote10ProRedmiNote10RedmiBookPro15锐龙版RedmiBookPro1千元以内备用机和老人机推荐,性价比神机?孩子的戒游神器?2021年了,每个人都会有一个属于自己的通讯工具,老人也不例外,但这个年代老人用实体按键少之又少,而用智能机也就越来越多而身为年轻的你,每天跑这跑那,就会出现主力机不够用的问题,或打造家庭KTV,麦克风是核心!唱吧K歌宝小巨蛋G2MAX麦克风评测现在各地疫情又有卷土重来之势,对我这种喜欢去KTV唱K的人来说无异于晴天霹雳。但没办法,唱K虽可贵,生命价更高。既然不能去KTV,那就在自己家里打造一个家庭KTV吧。组建家庭KTV