文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Boot 定时调度任务高级篇:调度任务的实现原理

2024-11-30 03:02

关注

核心问题

关于Springboot调度任务的工作原理,实际就是两个问题:

第一个问题,调度任务是如何被注册的?

第二个问题,注册的调度任务是如何触发执行的?

实现方法

关于Springboot调度任务的具体实现方法已经在上一篇文章中详细介绍过,这里再作一下简单的梳理、归纳,主要两种方法:

两种方法都需要使用@EnableScheduling(第一个核心关键类)来开启调度任务功能。

基于注解@Scheduled

基于注解@Scheduled内的属性,可以分为三类调度任务:

  1. cron表达式可以通过若干数字、空格、符号按一定的规则,组成一组字符串,定义调度任务的执行规则;
  2. fixedDelay以每次调度任务执行完成后间隔指定时间再开始下一次的调度任务,单位是毫秒;
  3. fixedRate以每次调度任务开始的时间间隔指定时间再开始下一次的调度任务,单位是毫秒;

基于接口SchedulingConfigurer

实现SchedulingConfigurer接口,并重写configureTasks()方法,在重写configureTasks()里,完成调度任务的注册;

工作原理

基于注解@Scheduled和基于接口SchedulingConfigurer接口,都需要使用@EnableScheduling来开启调度任务注册功能。进入@EnableScheduling注解内部观察一番,发现通过@Import引入了一个配置类SchedulingConfiguration.class(第二个核心关键类)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}

顺着SchedulingConfiguration.class进入其内部,又发现了一个大秘密:ScheduledAnnotationBeanPostProcessor(第三个核心关键类)

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {


   @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
      return new ScheduledAnnotationBeanPostProcessor();
   }


}

对Spring生命周期比较熟悉的话,一看到XxxxBeanPostProcessor,就能想到postProcessAfterInitialization()方法了。

基于注解@Scheduled与基于接口SchedulingConfigurer调度任务实现入口是一样的,其具体实现是不一样的。

基于注解@Scheduled

还记得第一个问题是什么吗?(调度任务是如何被注册的?)基于注解@Scheduled调度任务的注册就是在中实现在ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization()方法中实现的,具体的步骤是:

在spring容器中找出被注解@Scheduled.class,@Schedules.class标记过的方法

图片

接着遍历这些方法,而实际的调度任务注册逻辑也是从这里(processScheduled()方法)开始的

图片

在实现方法里已经梳理清楚了,springboot的调度任务实际上可以分为三类,在本文中就以常用cron表达式类为例来说明其注册、执行过程。进入processScheduled()内,首先把@Scheduled标记的方法包装成一个Runnable任务(实现java多线程的方法之一就是实现java.lang.Runnable接口)

图片

而在processScheduled()也会根据不同类别的任务分别作处理,这里以cron表达式类的调度任务为例看一下后续是怎么处理的。

图片

进入thsi.registrat.scheduleCronTask()方法内部(第五个核心关键类ScheduledTaskRegistrar),很多人认为下面就是触发开始执行调度任务的执行了;实际上这么认为是错的,因为这个时候Spring的容器还未启动完成,任务的调度器(this.taskScheduleer是null)还未实例化,所以这里只是完成调度任务的注册。

图片

分析源码就是这样,得慢慢来,不要急,要牢牢把握住自己的问题,千万不要迷路了。下面开始分析第二个问题:注册的调度任务是如何触发执行的。

任务注册上面说到了ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization()方法被触发执行,一直到执行到thsi.registrat.scheduleCronTask()只是完成了调度任务的注册,并没有开始执行,实际上注册完成的调度任务开始执行是在Spring容器启动完成后,会发布一个启动完成的事件(ContextRefreshedEvent),ScheduledAnnotationBeanPostProcessor实现了Spring的监听器接口(ApplicationListener),因此实际触发已注册调度任务的执行的入口是在监听方法中(ScheduledAnnotationBeanPostProcessor#onApplicationEvent);

图片

在finishRegistration()中,分别做了哪些事呢?

第一,调用ScheduledAnnotationBeanPostProcessor#resolveSchedulerBean()查找任务调度器(TaskScheduler);

第二,实际找到了ThreadPoolTaskScheduler作为实际的任务调度器,然后调用ScheduledTaskRegistrar#setTaskScheduler()完成任务调度器的配置;

第三,接着调用ScheduledTaskRegistrar#afterPropertiesSet()开始实际的任务触发执行;不同类型的调度任务是在ScheduledTaskRegistrar#scheduleTasks()中完成判断,然后分别调用各自的方法执行的;以cron表达式类型的调度任务为例,实际上最后由ScheduledTaskRegistrar#scheduleCronTask()实际完成。

至此,基于注解@Scheduled的调度任务实现原理基本分析完了,下面是我就调度任务的注册和执行两个时机为入口,绘制了整个过程的一个调用时序图,供大家参考学习:

图片

基于接口SchedulingConfigurer

基于接口SchedulingConfigurer的Springboot调度任务,与基于注解不同,其调度任务的注册、执行都是在Spring容器启动完成以后,发布ContextRefreshedEvent事件后,实现了Srping事件监听器的接口(ApplicationListener)的ScheduledAnnotationBeanPostProcessor类的onApplicationEvent()被触发,然后才开始调度任务的注册和执行,下面具体分析一下:

第一步,查找所有SchedulingConfigurer接口的实现类,然后遍历所有实现类并执行SchedulingConfigurer#configureTasks,就这么朴实无华,完成了所有通过实现SchedulingConfigurer接口(第四个核心关键类)的调度任务注册;(第一个问题:调度任务是如何被注册的,到这已经有答案了)

图片

第二步,从ScheduledTaskRegistrar#afterPropertiesSet()进入开始调度任务的触发执行阶段(第二个问题,注册的的调度任务是如何被执行的),afterPropertiesSet()中实际是调用了ScheduledTaskRegistrar#scheduleTasks()方法;

如果在实现SchedulingConfigurer接口,重写configureTasks(),没有显性的指定任务调度器(TaskScheduler),在scheduleTasks()里,会初始化一个默认的任务调度器,这里要注意,默认的使用的是单线程的线程池;

图片

接下来就是根据实际注册的调度任务类型分别开始调度任务的实际执行了,在上一篇文章中,我注册的是TriggerTasks类型的任务,所以这里就会调用ScheduledTaskRegistrar#scheduleTriggerTask()方法开始调度任务的执行。

图片

图片

至此,基于接口SchedulingConfigurer的Springboot调度任务的工作任务也基本分析完了,下面是整个过程的调用时序图,大家可以参考一下:

图片

核心类回顾

总结

通过分析Springboot两种调度任务的实现方法的工作原理,有什么收获呢?

第一,默认情况下,使用单线程的线程池来执行调度任务,性能上不会太高,适用场景有限;

第二,即便显性的任务调度器配置了拥用较多线程的线程池,与现有其他业务同处一个工程,也会挤占其他业务的服务器资源;

所以,在实际使用过程中,应根据实际场景和资源配置进行选择。

来源:凡夫编程内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯