Spring @Scheduled学习

一. Jdk中的定时任务

我们平时在 Spring 项目中会使用 @Scheduled 开启定时任务;

jdk 中其实也提供了定时任务线程池 ScheduledThreadPool,我们可以直接通过 Executors 工具类获取;

// 创建了核心线程数为 2 的 ScheduledThreadPool 对象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);executorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("正在执行任务1,线程名:" + Thread.currentThread().getName());}
}, 0, 3, TimeUnit.SECONDS);executorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("正在执行任务2,线程名:" + Thread.currentThread().getName());}
}, 0, 3, TimeUnit.SECONDS);

这里会启用两个线程去执行定时任务,打印如下;

正在执行任务1,线程名:pool-1-thread-1
正在执行任务2,线程名:pool-1-thread-1
正在执行任务1,线程名:pool-1-thread-1
正在执行任务2,线程名:pool-1-thread-2

二. Spring中的定时任务

我们知道:要开启 Spring 的定时任务,也就是要使用 @Scheduled 注解的话,需要 @EnableScheduling 启用定时任务;

下面我们从源码的角度来看一下 Spring 中的定时任务;

1. Spring中默认的TaskScheduler

Spring 项目中会存在一个默认的 taskScheduler 对象,它是一个 ThreadPoolTaskScheduler;

是在 TaskSchedulingAutoConfiguration 中导入的,可以看到会往 Spring 容器中注入一个 beanName 叫 “taskScheduler” 的 ThreadPoolTaskScheduler 对象;

@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@AutoConfiguration(after = TaskExecutionAutoConfiguration.class)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
public class TaskSchedulingAutoConfiguration {@Bean@ConditionalOnBean(name = "internalScheduledAnnotationProcessor")@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {// 通过 TaskSchedulerBuilder.build() 构建 ThreadPoolTaskScheduler 对象return builder.build();}@Bean@ConditionalOnMissingBeanpublic TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,                                                   ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {// 创建出 TaskSchedulerBuilderTaskSchedulerBuilder builder = new TaskSchedulerBuilder();// 设置 TaskSchedulerBuilder 的核心线程数builder = builder.poolSize(properties.getPool().getSize());Shutdown shutdown = properties.getShutdown();builder = builder.awaitTermination(shutdown.isAwaitTermination());// 设置 TaskSchedulerBuilder 的线程名前缀builder = builder.threadNamePrefix(properties.getThreadNamePrefix());builder = builder.customizers(taskSchedulerCustomizers);// 返回 TaskSchedulerBuilder 对象return builder;}
}

2. @Scheduled

我们需要知道是哪个类来解析 @Scheduled 注解的;

/*** 从注解的信息可以看出解析是在 ScheduledAnnotationBeanPostProcessor* 我们需要重点看 ScheduledAnnotationBeanPostProcessor 类** @see EnableScheduling* @see ScheduledAnnotationBeanPostProcessor* @see Schedules*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {long fixedDelay() default -1;long fixedRate() default -1;String cron() default "";
}

那这个 ScheduledAnnotationBeanPostProcessor 又是从哪注入到 Spring 容器的呢?其实是在 @EnableScheduling 中注入的;

3. @EnableScheduling

我们看一下 @EnableScheduling,它往 spring 容器中注入了一个配置类:SchedulingConfiguration;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)// 往 Spring 容器中注入 SchedulingConfiguration 类
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {}

我们看一下 SchedulingConfiguration 类,它往容器中注入了 ScheduledAnnotationBeanPostProcessor 对象;

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {// 往容器中注入了 ScheduledAnnotationBeanPostProcessor 对象// 并且它的 beanName 为 "internalScheduledAnnotationProcessor"// 正因为导入了 "internalScheduledAnnotationProcessor",// taskScheduler 对象才会被注入到 Spring 容器中@Bean(name = "internalScheduledAnnotationProcessor")public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();}}

至此,前置完成,解析 @Scheduled 注解的任务交给了 ScheduledAnnotationBeanPostProcessor,我们需要重点看 ScheduledAnnotationBeanPostProcessor 做了啥;

三. ScheduledAnnotionBeanPostProcessor

  1. ScheduledAnnotionBeanPostProcessor 实现了 BeanPostProcessor 接口,它的 postProcessAfterInitialization() 会解析 bean 中的 @Scheduled 注解;
  2. ScheduledAnnotionBeanPostProcessor 实现了 ApplicationListener 接口,它的 onApplication(ContextRefreshedEvent event) 会在 spring 刷新完 beanFactory 容器的时候调用,启用定时任务;

我们也主要从这两个方法入手;

1. postProcessAfterInitialization()

postProcessAfterInitialization() 如下;

// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
public Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler || bean instanceof ScheduledExecutorService) {return bean;}Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);// 1. 遍历类中的每一个方法,收集带有 @Scheduled 注解的方法if (AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {Map<Method, Set<Scheduled>> annotatedMethods;// 构建 annotatedMethodsif (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);} else {// 2. 轮询 annotatedMethods// 对 @Scheduled 注解的方法执行 processScheduled()annotatedMethods.forEach((method, scheduledAnnotations) ->scheduledAnnotations.forEach(scheduled ->                             						processScheduled(scheduled, method, bean)));}}return bean;
}

processScheduled(scheduled, method, bean) 中,scheduled 为方法上的 @Scheduled 对象;我们看下 processScheduled() 的过程,我们只关注用的多的 cron 表达式;

// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {// 1. 将 bean 和 method 包装为 runnable 对象Runnable runnable = createRunnable(bean, method);boolean processedSchedule = false;Set<ScheduledTask> tasks = new LinkedHashSet<>(4);// 2. 解析 cron 表达式String cron = scheduled.cron();if (StringUtils.hasText(cron)) {String zone = scheduled.zone();if (this.embeddedValueResolver != null) {cron = this.embeddedValueResolver.resolveStringValue(cron);zone = this.embeddedValueResolver.resolveStringValue(zone);}processedSchedule = true;if (!Scheduled.CRON_DISABLED.equals(cron)) {TimeZone timeZone;if (StringUtils.hasText(zone)) {timeZone = StringUtils.parseTimeZoneString(zone);}else {timeZone = TimeZone.getDefault();}// 3. 往 this.registrar 中添加 CronTask 任务// this.registrar 为 ScheduledTaskRegistrar 类对象tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));}}
}// ------------------------ ScheduledTaskRegistrar -------------------------
public ScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {// 1. 第一次进来的时候创建 scheduledTaskscheduledTask = new ScheduledTask(task);newTask = true;}// 2. 第一次进来的时候创建 this.taskScheduler == null,走 else 逻辑if (this.taskScheduler != null) {scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());} else {// 3. 将 task 放入到当前的 cronTask 中addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}

至此,bean 中的 @Scheduled 注解的定时任务都被包装为 cronTask 对象放入到 ScheduledTaskRegistrar 中;

2. onApplication(ContextRefreshEvent)

Spring 刷新完 beanFactory 容器的时候会调用该方法,启用定时任务;

// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
public void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.applicationContext) {// 调用 finishRegistration()finishRegistration();}
}// -------------------- ScheduledAnnotionBeanPostProcessor ---------------------
private void finishRegistration() {if (this.scheduler != null) {this.registrar.setScheduler(this.scheduler);}// 1. 先查找 SchedulingConfigurer,如果有的话用 SchedulingConfigurerif (this.beanFactory instanceof ListableBeanFactory) {Map<String, SchedulingConfigurer> beans =((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());AnnotationAwareOrderComparator.sort(configurers);for (SchedulingConfigurer configurer : configurers) {configurer.configureTasks(this.registrar);}}if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {try {// 2. 寻找 TaskScheduler bean,通过 byType 的方式// 往 this.registrar 中设置定时线程池this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));} catch (NoUniqueBeanDefinitionException ex) {// 2.1 TaskScheduler bean 不唯一,通过 byName 的方式,注入 "taskScheduler"// 寻找 TaskScheduler bean,通过 byName 的方式// 一般注入的都是默认的 "taskScheduler"this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));} catch (NoSuchBeanDefinitionException ex) {try {// 3. 寻找 ScheduledExecutorService bean,通过 byType 的方式this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));} catch (NoUniqueBeanDefinitionException ex2) {// 3.1 ScheduledExecutorService bean 不唯一// 寻找 ScheduledExecutorService bean,通过 byName 的方式this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));} catch (NoSuchBeanDefinitionException ex2) {logger.info("没有 TaskScheduler/ScheduledExecutorService bean")}}}// 4. 上述往 this.registrar 中设置了 taskScheduler 对象// 执行 this.registrar.afterPropertiesSet()this.registrar.afterPropertiesSet();
}

我们看下 this.registrar.afterPropertiesSet() 做了啥;

// ------------------------ ScheduledTaskRegistrar -------------------------
public void afterPropertiesSet() {scheduleTasks();
}// ------------------------ ScheduledTaskRegistrar -------------------------
protected void scheduleTasks() {// 1. 如果 this.taskScheduler == null// 创建单核心线程的 ThreadScheduledExecutor 作为定时线程池// 一般 this.taskScheduler 不会为 nullif (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));}}// 2. this.cronTasks 不为 null// 依次遍历 cronTask,执行 scheduleCronTask(cronTask)// 这是我们第二次进入 scheduleCronTask(cronTask),和第一次有点区别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));}}
}// ------------------------ ScheduledTaskRegistrar -------------------------
public ScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {scheduledTask = new ScheduledTask(task);newTask = true;}// 1. 第二次进来,this.taskScheduler 不为 null// 执行 this.taskScheduler.schedule(),正式启动定时任务if (this.taskScheduler != null) {scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());} else {addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);
}

至此,SpringScheduled 全部解析完毕;

四. 配置定时线程池

1. 通过配置文件

我们在 TaskSchedulingAutoConfiguration 中知道,其实默认的 ThreadPoolTaskScheduler 都是根据配置项 TaskSchedulingProperties 创建的,默认核心线程 coreThreads = 1;

可以进行如下配置:

spring:task:scheduling:pool:size: 5thread-name-prefix: my-schedule-

2. 自定义ThreadPoolTaskScheduler

我们也可以直接往 Spring 容器中自定义注入 ThreadPoolTaskScheduler 对象,只不过需要注意它的 beanName 必须为 taskScheduler;

@Configuration
public class ThreadPoolConfig {@Beanpublic Executor taskScheduler() {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();taskScheduler.setPoolSize(3);taskScheduler.setThreadNamePrefix("my-schedule-task-");taskScheduler.initialize();return taskScheduler;}
}

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

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

相关文章

ROS2 + 科大讯飞 初步实现机器人语音控制

环境配置&#xff1a; 电脑端&#xff1a; ubuntu22.04实体机作为上位机 ROS版本&#xff1a;ros2-humble 实体机器人&#xff1a; STM32 思岚A1激光雷达 科大讯飞语音SDK 讯飞开放平台-以语音交互为核心的人工智能开放平台 实现步骤&#xff1a; 1. 下载和处理科大讯飞语音模…

开发指南048-前端模块版本

平台前端框架内置了一个文件version.vue <template> <div> <br> 应用名称: {{name}} <br> 当前版本&#xff1a;{{version}} <br> 服务网关: {{gateway}} </div> </template> <scrip…

qt 创建一个包含两按钮,且安装和自定义控件间没有间距

在 Qt 中创建一个包含两个按钮且按钮之间没有间距的自定义控件&#xff0c;你可以使用 QHBoxLayout 或 QVBoxLayout&#xff08;取决于你希望按钮是水平排列还是垂直排列&#xff09;&#xff0c;并设置布局的间距为 0。以下是一个简单的示例&#xff0c;展示了如何创建一个水平…

Dataset for Stable Diffusion

1.Dataset for Stable Diffusion 笔记来源&#xff1a; 1.Flickr8k数据集处理 2.处理Flickr8k数据集 3.Github&#xff1a;pytorch-stable-diffusion 4.Flickr 8k Dataset 5.dataset_flickr8k.json 1.1 Dataset 采用Flicker8k数据集&#xff0c;该数据集有两个文件&#xff…

Node.js_mongodb用户名和密码操作

mongodb用户名和密码操作 查看用户密码创建管理员用户和密码mongodb的目标是实现快速简单部署,所以存在很多安全问题 默认配置下没有用户和密码,无需身份验证即可登录,不像mysql那样需要登录才能操作数据库本身安全问题:升级3.0以上版本查看用户密码 密码是加密存储的,并且…

前端工程化10-webpack静态的模块化打包工具之各种loader处理器

9.1、案例编写 我们创建一个component.js 通过JavaScript创建了一个元素&#xff0c;并且希望给它设置一些样式&#xff1b; 我们自己写的css,要把他加入到Webpack的图结构当中&#xff0c;这样才能被webpack检测到进行打包&#xff0c; style.css–>div_cn.js–>main…

速盾:ddos高防ip哪里好用?

随着互联网的飞速发展&#xff0c;DDoS攻击问题逐渐突出。DDoS攻击是一种通过在网络上创建大量请求&#xff0c;使目标网络或服务器过载而无法正常工作的攻击方式。为了应对DDoS攻击&#xff0c;提高网络的安全性和稳定性&#xff0c;使用高防IP成为了一种常见的解决办法。 DD…

Flower花所比特币交易及交易费用科普

在加密货币交易中&#xff0c;选择一个可靠的平台至关重要。Flower花所通过提供比特币交易服务脱颖而出。本文将介绍在Flower花所进行比特币交易的基础知识及其交易费用。 什么是Flower花所&#xff1f; Flower花所是一家加密货币交易平台&#xff0c;为新手和资深交易者提供…

【C++】开源:drogon-web框架配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍drogon-web框架配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;…

Linux系统编程-线程同步详解

线程同步是指多个线程协调工作&#xff0c;以便在共享资源的访问和操作过程中保持数据一致性和正确性。在多线程环境中&#xff0c;线程是并发执行的&#xff0c;因此如果多个线程同时访问和修改共享资源&#xff0c;可能会导致数据不一致、竞态条件&#xff08;race condition…

面试题008-Java-SpringBoot

面试题008-Java-SpringBoot 目录 面试题008-Java-SpringBoot题目自测题目答案1. Spring 和 Spring Boot有什么区别&#xff1f;2. Spring Boot 的主要优点是什么&#xff1f;3. 什么是Spring Boot Starter&#xff1f;4. 介绍一下SpringBootApplication注解&#xff1f;5. Spri…

【密码学】消息认证

你发送给朋友一条消息&#xff08;内容&#xff1a;明天下午来我家吃饭&#xff09;&#xff0c;这一过程中你不想让除你朋友以外的人看到消息的内容&#xff0c;这就叫做消息的机密性&#xff0c;用来保护消息机密性的方式被叫做加密机制。 现在站在朋友的视角&#xff0c;某一…

使用PyQt5实现添加工具栏、增加SwitchButton控件

前言&#xff1a;通过在网上找到的“电池电压监控界面”&#xff0c;学习PyQt5中添加工具栏、增加SwitchButton控件&#xff0c;在滑块控件右侧增加文本显示、设置界面背景颜色、修改文本控件字体颜色等。 1. 上位机界面效果展示 网络上原图如下&#xff1a; 自己使用PyQt5做…

springboot异常(一):springboot自定义全局异常处理

&#x1f337;1. 自定义一个异常类 自定义一个异常&#xff0c;有两个变量异常代码、异常消息&#xff0c;定义了两个构造方法&#xff0c;一个无参构造方法&#xff0c;一个所有参数构造方法。 在构造方法中要掉用父类的构造方法&#xff0c;主要目的是在日志或控制台打印异…

【Linux】多线程_3

文章目录 九、多线程3. C11中的多线程4. 线程的简单封装 未完待续 九、多线程 3. C11中的多线程 Linux中是根据多线程库来实现多线程的&#xff0c;C11也有自己的多线程&#xff0c;那它的多线程又是怎样的&#xff1f;我们来使用一些C11的多线程。 Makefile&#xff1a; te…

Linux - 探索命令行

探索命令行 Linux命令行中的命令使用格式都是相同的: 命令名称 参数1 参数2 参数3 ...参数之间用任意数量的空白字符分开. 关于命令行, 可以先阅读一些基本常识. 然后我们介绍最常用的一些命令: ls用于列出当前目录(即"文件夹")下的所有文件(或目录). 目录会用蓝色…

面试经典题型:调用HashMap的put方法的具体执行流程

在调用put方法时时&#xff0c;有几个关键点需要考虑&#xff1a; 哈希冲突的发生与解决&#xff1a; 哈希冲突指不同的键通过哈希函数计算得到相同的哈希值&#xff0c;导致它们应该存放在哈希表的同一个位置。解决冲突的常用方法包括开放寻址法和链表法&#xff08;或其升级形…

CSIP-FTE考试专业题

靶场下载链接&#xff1a; https://pan.baidu.com/s/1ce1Kk0hSYlxrUoRTnNsiKA?pwdha1x pte-2003密码&#xff1a;admin123 centos:root admin123 解压密码&#xff1a; PTE考试专用 下载好后直接用vmware打开&#xff0c;有两个靶机&#xff0c;一个是基础题&#x…

【CTF-Crypto】数论基础-02

【CTF-Crypto】数论基础-02 文章目录 【CTF-Crypto】数论基础-021-16 二次剩余1-20 模p下-1的平方根*1-21 Legendre符号*1-22 Jacobi符号*2-1 群*2-2 群的性质2-3 阿贝尔群*2-4 子群2-11 群同态2-18 原根2-21 什么是环2-23 什么是域2-25 子环2-26 理想2-32 多项式环 1-16 二次剩…

打造智慧校园德育管理,提升学生操行基础分

智慧校园的德育管理系统内嵌的操行基础分功能&#xff0c;是对学生日常行为规范和道德素养进行量化评估的一个创新实践。该功能通过将抽象的道德品质转化为具体可量化的指标&#xff0c;如遵守纪律、尊师重道、团结协作、爱护环境及参与集体活动的积极性等&#xff0c;为每个学…