spring使用@Scheduled配置定时任务

schedule的使用

在Spring中,你可以使用@Scheduled注解来标记一个方法,使其成为一个定时任务。一般情况下@Scheduled注解修饰的任务方法没有返回值也没有入参。

开启schedule
@Configuration
@EnableScheduling
public class ScheduleConfig {}

在配置类上使用@EnableScheduling注解开启schedule。

fixedDelay固定间隔
@Scheduled(fixedDelay = 2000)public void fixDelay(){long workTime = 1000*(new Random().nextInt(10));log.info("fixDelay,workTime:{}",workTime);try {Thread.sleep(workTime);} catch (InterruptedException e) {e.printStackTrace();}}

fixedDelay : 固定间隔时间执行,单位毫秒

这里固定间隔指:上次调用结束和下次调用开始之间的时间间隔。会确保两个任务之间有间隔,所以fixedDelay的任务不会有同时多个任务执行。

fixedRate固定周期
@Scheduled(fixedRate = 2000)
public void fixRate(){long workTime = 1000*(new Random().nextInt(10));log.info("fixRate,workTime:{}",workTime);try {Thread.sleep(workTime);} catch (InterruptedException e) {e.printStackTrace();}
}

fixedRate: 和fixedDelay差不多,固定频率执行。但是同样的任务不会并行执行,如果超过设定的频率,前一个任务还未完成则会等待前一个任务完成,再开始下一个任务。但是不能像fixedDelay一样保证每个任务之间又固定的间隔。比如:fixedRate设置的任务周期是5秒,如果一次任务的时间耗时是3秒,则下一任务会等到5秒间隔开始,但是如果惹我你执行耗时是6秒,则本次任务结束后会立即执行下一次任务,下一任务已经排队等候1秒了。

initialDelay参数:

 @Scheduled(fixedDelay = 2000,initialDelay = 3000)public void initDelay(){log.info("initDelay");}

initialDelay可以设置任务延迟多长时间后开启。

cron表达式
@Scheduled(cron = "5 * * * * ?")
public void cron(){log.info("cron");
}

cron表达式指定任务频率。表达式格式:[秒 分 时 天 月 周]。如上面的表达式意思逢5秒时候执行,也即每一分钟执行一次。

时区设置:默认情况下spring使用系统默认时区,也可以通过zone参数来指定时区。例如

@Scheduled(cron="5 * * * * ?",zone = "Asia/Shanghai")
参数化任务时间

schedule不管是fixedRate、fixedDelay 还是cron表达式,其指定时间参数不仅可以直接在注解上配置固定值活表达式,还可以从properties参数中来进行动态加载。

@Scheduled(fixedDelayString = "${shchedule.fixDelay}");
@Scheduled(fixedRateString = "${shchedule.fixRate}")
@Scheduled(cron = "${shchedule.cron}")

这样可以通过SPEL表达式从容器的配置中进行加载,可以方便的修改任务的执行计划。

源码分析

@EnableScheduling注解import一个配置类SchedulingConfiguration,然后该配置类又引入一个ScheduledAnnotationBeanPostProcessor后置处理器。前面文章有说后置处理器的after方法在bean初始化完成后会被调用。

任务的解析

ScheduledAnnotationBeanPostProcessor是一个bean处理器,那就要看他的后置处理方法postProcessAfterInitialization()

public Object postProcessAfterInitialization(Object bean, String beanName) {//跳过线程池beanif (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||bean instanceof ScheduledExecutorService) {// Ignore AOP infrastructure such as scoped proxies.return bean;}Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);if (!this.nonAnnotatedClasses.contains(targetClass) &&AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);});if (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);}else {// Non-empty set of methodsannotatedMethods.forEach((method, scheduledAnnotations) ->scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));}}return bean;
}

找出所有带有@Schedules注解的方法Map<Method, Set> annotatedMethods ,然后逐个调用processScheduled()方法进行处理任务。

processScheduled()方法就是解析@Schedules上注解配置的信息,进行封装成任务,最后存放到 ScheduledAnnotationBeanPostProcessor的scheduledTasks变量里。

Map<Object, Set<ScheduledTask>> scheduledTasks;
ScheduledTaskRegistrar registrar = ScheduledTaskRegistrar()

不同的任务类型会封装成不同的Task类型(FixedDelayTask、FixedRateTask、CronTask)。

ScheduledAnnotationBeanPostProcessor中还有一个ScheduledTaskRegistrar类型的registrar变量,不同类型的任务在该类中也有一份存储:

ScheduledTaskRegistrar{private TaskScheduler taskScheduler;private List<TriggerTask> triggerTasks;private List<IntervalTask> fixedRateTasks;private List<CronTask> cronTasks;
}

这个时候任务只是被抽取出来了,还没有被执行。因为还没看到TaskScheduler任务执行器初始化,那么什么时候开始执行呢?

任务的执行

ScheduledAnnotationBeanPostProcessor不仅是一个后置处理器,还实现了ApplicationListener接口。当容器发布refresh事件时,会回调其onApplicationEvent方法。前面的文章也只读spring在初始化时候有一个最重要的onrefresh方法来进行所有bean的初始化,在最后完成时会发送该事件。

public void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.applicationContext) {finishRegistration();}
}

来分析下finishRegistration()源码逻辑,这里就不贴代码了

1、判断当前容器beanFacotry是否是ListableBeanFactory,如果是就拿出所有SchedulingConfigurer类型的bean依次调用其configureTasks()方法。

这里也是我们自定人任务执行一些参数的入口,即通过实现SchedulingConfigurer接口。

2、获取TaskScheduler

step1、首先从beanFacotry尝试获取TaskScheduler类型的的bean。

如果有多个TaskScheduler类型的bean。 则尝试按bean名称为taskScheduler的再进行获取。如果没有则认为没有。

step2、如果没有TaskScheduler则尝试获取ScheduledExecutorService类型的bean,同样多个再按scheduledExecutorService名称获取一次,没有则认为没有。

3、调用registrar.afterPropertiesSet()方法

这里最终会调用registrar的scheduleTasks();

如果此时taskScheduler还是null,即上面的第2步没有从容器中获取到TaskScheduler。则会默认创建一个taskScheduler。其对应的线程池是Executors.newSingleThreadScheduledExecutor()。

看到这里就能够明白为什么默认情况schedule任务都是一个个串行执行的了,因为默认线程池是singleThread。

还有延迟一定时间执行,周期执行这都是java ScheduledThreadPoolExecutor所支持的。

ScheduledTaskRegistrar#scheduleTasks代码有必要看以下。

protected void scheduleTasks() {if (this.taskScheduler == null) {this.localExecutor = Executors.newSingleThreadScheduledExecutor();this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);}if (this.triggerTasks != null) {for (TriggerTask task : this.triggerTasks) {addScheduledTask(scheduleTriggerTask(task));}}if (this.cronTasks != null) {for (CronTask task : this.cronTasks) {addScheduledTask(scheduleCronTask(task));}}if (this.fixedRateTasks != null) {for (IntervalTask task : this.fixedRateTasks) {addScheduledTask(scheduleFixedRateTask(task));}}if (this.fixedDelayTasks != null) {for (IntervalTask task : this.fixedDelayTasks) {addScheduledTask(scheduleFixedDelayTask(task));}}
}

这里就是拿出所有的Task来进行执行了,最后任务执行都是通过taskScheduler来执行的。

ConcurrentTaskScheduler几个启动不同任务方法:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {long initialDelay = startTime.getTime() - this.clock.millis();return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
}public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {long initialDelay = startTime.getTime() - this.clock.millis();return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS);}public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {if (this.enterpriseConcurrentScheduler) {return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);}else {ErrorHandler errorHandler =(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));return new ReschedulingRunnable(task, trigger, this.clock, this.scheduledExecutor, errorHandler).schedule();}}}

FixedRate和FixedDelay就不必看了,是之间丢给scheduledExecutor线程池去执行的,这里看下cron类型最后会调用schedule()方法。这里看其else分支使用ReschedulingRunnable类进行执行。

ReschedulingRunnable#schedule()

public ScheduledFuture<?> schedule() {synchronized (this.triggerContextMonitor) {//根据cron表达式计算任务执行间隔this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);if (this.scheduledExecutionTime == null) {return null;}long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();//延迟执行任务this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);return this;}
}

最后也是交给线程池schedule方法执行。那么是怎么实现cron周期性执行的呢?

这里延迟执行的任务是this(ReschedulingRunnable),到时间就会调用其run方法,来看run方法逻辑

ReschedulingRunnable#run()

public void run() {Date actualExecutionTime = new Date(this.triggerContext.getClock().millis());//原业务逻辑super.run();Date completionTime = new Date(this.triggerContext.getClock().millis());synchronized (this.triggerContextMonitor) {Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");//更新triggerthis.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);if (!obtainCurrentFuture().isCancelled()) {//任务未被取消schedule();//再次执行schedule}}
}

这里看到到时间run方法首先会调用原被包装的业务逻辑。然后更新trigger信息,再次调用schedule()方法生成下一周期任务到executor执行,到时间又会调用run方法这样循环往复完成cron周期性执行。

执行器配置

从任务的执行部分逻辑可以知道,我们有好几个位置可以设置taskScheduler。

1、通过实现SchedulingConfigurer接口

public class MySchedulingConfigurer implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {//设置taskSchedulerThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(5);taskScheduler.setThreadNamePrefix("taskScheduler");taskScheduler.initialize();taskRegistrar.setTaskScheduler(taskScheduler);//动态添加任务taskRegistrar.addFixedRateTask(new IntervalTask(()->{log.info("new add task");},5000,0));}
}

这里再运行观察日志,会看到是使用的自定义线程池进行执行任务,最后新加的任务也正常执行。

17:49:47.368 [taskScheduler2] INFO com.cpx.service.schedule.MySchedulingConfigurer - new add task
17:49:52.368 [taskScheduler2] INFO com.cpx.service.schedule.MySchedulingConfigurer - new add task
17:49:57.369 [taskScheduler1] INFO com.cpx.service.schedule.MySchedulingConfigurer - new add task
17:50:02.369 [taskScheduler1] INFO com.cpx.service.schedule.MySchedulingConfigurer - new add task
17:50:05.008 [taskScheduler4] INFO com.cpx.service.schedule.ScheduleService - cron

2、通过直接定义executor类型bean的方式。

前面ScheduledAnnotationBeanPostProcessor后置处理器获取taskScheduler过程中可以知道。我们可以直接在Configuration中定义TaskScheduler类型或ScheduledExecutorService类型的bean即可。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

B树和B+树的区别

1.节点结构&#xff1a; a.B树&#xff1a; B树的每个节点包含键和对应的值&#xff0c;子节点的数量和键的数量相等。在B树中&#xff0c;每个节点都存储键和值&#xff0c;并且非叶子节点的键值对应于其子节点的范围。 b.B树&#xff1a; B树的非叶子节点只包含键&#xff0…

Modbus RTU协议与S7 200 PLC通讯

一、Modbus RTU功能码 二、功能码使用与解析实例 01功能码 –读线圈状态 主机发送&#xff1a;01 01 00 01 00 08 6C 0C 从机回复: 01 01 01 2F 10 54 主机解析&#xff1a;01 地址(设备ID)&#xff1b; 01 功能码&#xff1b;00 01 代表查询的起始线圈地址&#xff0c;即…

二叉树题目:输出二叉树

文章目录 题目标题和出处难度题目描述要求示例数据范围 前言解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;输出二叉树 出处&#xff1a;655. 输出二叉树 难度 6 级 题目描述 要求 给定二叉树的根结点 root \textt…

链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)

在上一篇文章中&#xff0c;我们探索了顺序表这一基础的数据结构&#xff0c;它提供了一种有序存储数据的方法&#xff0c;使得数据的访 问和操作变得更加高效。想要进一步了解&#xff0c;大家可以移步于上一篇文章&#xff1a;探索顺序表&#xff1a;数据结构中的秩序之美 今…

06.仿简道云公式函数实战-前瞻

1.前言 在上篇文章中&#xff0c;我们介绍了QLExpress的进阶知识&#xff0c;扩展操作符&#xff0c;自定义操作符和自定义函数等内容。学了上面的内容后&#xff0c;目前对于QLExpress使用已经问题不大&#xff0c;从这篇文章&#xff0c;我们就进入我们的主题仿简道云公式函…

mysql索引优化案例

案例1 select * from order where user_id 11 and status 1 and id > 10000 limit 10 2个索引 user_id 、 id 场景 偶然会查询的慢&#xff0c;且不容易复现 原因 id大的时候&#xff0c;mysql评估后使用id更快&#xff1b;但是实际上会多几次IO查询 &#x…

C++的面向对象学习(3):面向对象编程的三大特性之:封装

文章目录 前言:C具有面向对象编程的特性一、根据前言的例子可以看出&#xff0c;对一个类的设计叫做封装。(1)例一&#xff1a;设计一个圆类&#xff0c;求圆的周长封装就是对一个类里面的数据属性和函数进行设计定义。对象就是主程序中对这个类的实例化。 二、类的封装怎么感觉…

CentOS:Docker容器中安装vim

在使用docker容器时&#xff0c;里边没有安装vim时&#xff0c;敲vim命令时提示说&#xff1a;vim: command not found 这个时候就须要安装vim&#xff0c;安装命令&#xff1a; apt-get install vim 出现以下错误&#xff1a; 解决方法&#xff1a; apt-get update 这个命令的…

Spark中使用scala完成数据抽取任务 -- 总结

如题 任务二&#xff1a;离线数据处理&#xff0c;校赛题目需要使用spark框架将mysql数据库中ds_db01数据库的user_info表的内容抽取到Hive库的user_info表中&#xff0c;并且添加一个字段设置字段的格式 第二个任务和第一个的内容几乎一样。 在该任务中主要需要完成以下几个阶…

[Java][File]使用mkdir以及CreateNewFile来制作游戏存档的分级目录+异常抛出机制

文件夹分类&#xff1a; Resource\\Image Resource\\Voice Resource\\VideoInformation\\Characters Information\\mobs Information\\BackpacksPlugin\\Serves Plugin\\Consumers文件的分类&#xff1a; Information\\Characters\\Male.txt Information\\Characters\\Female.t…

刷题记录第五十一天-去除重复字母

题目要求的是字典序最小的结果。只需要理解一点就是按大小顺序排列的字符串的字典序就是最小的&#xff0c;如“abcd”这种。 解题思路如下&#xff1a; 首先明确要使用栈结构&#xff0c;并且是从栈底到栈顶递增&#xff0c;要尽可能保证递增&#xff0c;这样就能保证字典序最…

前端项目常用函数封装(二)

文章目录 前端项目常用函数封装(一)判断两个数组是否有相同元素 返回相同元素&#xff08;数组&#xff09;判断hex颜色值是深色还是浅色随机生成深浅样色 js判断是手机端还是移动端使用UA判断使用媒体查询判断 fetch直接读文件内容&#xff0c;解决乱码问题下载文件将字符串下…

ansibe的脚本---playbook剧本(1)

playbook剧本组成部分&#xff1a; 1、task 任务&#xff1a; 主要是包含要在目标主机上的操作&#xff0c;使用模块定义操作。每个任务都是模块的调用。 2、variables变量&#xff1a;存储和传递数据。变量可自定义&#xff0c;可以在playbook中定义为全局变量&#xff0c;可…

深入理解 Spring Boot:核心知识与约定大于配置原则

深入理解 Spring Boot&#xff1a;核心知识与约定大于配置原则 简单说一下为什么要有 Spring Boot&#xff1f; 因为 Spring 的缺点。 虽然 Spring 的组件代码是轻量级的&#xff0c;但它的配置却是重量级的(需要大量 XML 配置) 为了减少配置文件&#xff0c;简化开发 Spri…

HarmonyOS应用事件打点开发指导

简介 传统的日志系统里汇聚了整个设备上所有程序运行的过程流水日志&#xff0c;难以识别其中的关键信息。因此&#xff0c;应用开发者需要一种数据打点机制&#xff0c;用来评估如访问数、日活、用户操作习惯以及影响用户使用的关键因素等关键信息。 HiAppEvent 是在系统层面…

手机数码品牌网站建设的作用是什么

手机数码产品几乎已经成为成年人必备的&#xff0c;包括手机、电脑、摄像机、键盘配件等&#xff0c;同时市场中相关企业也非常多&#xff0c;消费者可供选择的商品类型也很多样&#xff0c;而对企业来讲&#xff0c;只有不断提升品牌形象、获客拉新等才能不断提升企业地位&…

istio工作负载

目录 文章目录 目录本节实战前言1、WorkloadEntry多实例不同端口权重位置 2、WorkloadGroup关于我最后 本节实战 实战名称&#x1f6a9; 实战&#xff1a;WorkloadEntry测试-2023.12.21(测试成功) 前言 在之前的章节中我们已经多次提到了工作负载&#xff0c;在 Istio 中工作…

《Effective C++》条款42

了解typename的双重意义 作为类模板而言class和typename是一样的。 template <class T> class A {}; template <typename T> class A {}; template <typename C> class A {... private:C::const_iterator* x; }; 这段代码看起来没啥问题。因为我们已经知道它…

CSS中设置盒子大小-box-sizing,设置盒子阴影- box-shadow ,设置盒子圆角-border-radius,属性及其属性值

一&#xff1a;设置盒子大小-box-sizing 默认情况下&#xff1a;盒子可见宽的大小由内容区&#xff0c;内边距&#xff0c;边框共同决定 box-sizing 属性用来设置盒子尺寸的计算方式&#xff0c; 也就是 width/height 指的是谁 可选值&#xff1a; content-box 内容区 默认值…

机器学习之神经结构搜索(Neural Architecture Search,NAS)附代码

概念 神经结构搜索(Neural Architecture Search,NAS)是一种自动化机器学习技术,它旨在通过搜索神经网络的结构空间来找到最优的网络架构,以解决特定的任务。通常,这个搜索过程可以通过强化学习、进化算法、遗传算法或其他优化方法来完成。神经结构搜索的目标是提高神经网…