SpringBoot启动流程之运行时监听器

SpringBoot启动过程:
springboot启动过程
上一节我们讨论SpringApplication实例化的过程,也就是上图1-5步骤,本节我们讨论6-9的关键步骤,现在主要讲是run方法里面的过程

   /*** 启动方法* @param args* @return*/public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;this.configureHeadlessProperty();//获取所有的启动监听器SpringApplicationRunListeners listeners = this.getRunListeners(args);//发布正在开始的事件listeners.starting();try {//包装参数变量ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//准备环境ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);Banner printedBanner = this.printBanner(environment);context = this.createApplicationContext();this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);this.refreshContext(context);this.afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var9) {this.handleRunFailure(context, var9, listeners);throw new IllegalStateException(var9);}try {listeners.running(context);return context;} catch (Throwable var8) {this.handleRunFailure(context, var8, (SpringApplicationRunListeners)null);throw new IllegalStateException(var8);}}

获取并启用监听器

这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情

  • 创建所有 SpringBoot 运行监听器并发布应用启动事件
  • 启用监听器

获取运行监听器

我们知道上一节我们在实例化SpringApplication的时候有获取所有监听器的动作,这里的获取监听器,是获取SpringBoot的启动监听器,完成不是一个概念的,这样我们就可以在SpringBoot启动过程中,嵌入我们扩展点,比如在SpringBoot启动时加载我们字典值,或者是环境需要的必要配置等,都可以。获取运行监听器代码如下:

    /*** 包装所有运行监听器,返回监听器 manger* @param args* @return*/private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class[]{SpringApplication.class, String[].class};return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}

从代码我们可以看出来,也是重复的代码调用,从META-INF/spring.factories获取所有的实现类,我先看看具体有哪些方法:

    public interface SpringApplicationRunListener {//正则启动作用的逻辑default void starting() {}//环境准备后,发布的时间方法default void environmentPrepared(ConfigurableEnvironment environment) {}//容器准备前,调用的逻辑default void contextPrepared(ConfigurableApplicationContext context) {}//容器加载后,调用的逻辑default void contextLoaded(ConfigurableApplicationContext context) {}//SpringBoot 开始后方法调用完后逻辑default void started(ConfigurableApplicationContext context) {}//SpringBoot 运作中调用的逻辑default void running(ConfigurableApplicationContext context) {}//失败调用的逻辑default void failed(ConfigurableApplicationContext context, Throwable exception) {}}

我们可以自己现实上面的启动监听器,然后再resources创建META-INF/spring.factories文件,把key全限定名接口,值是我们自定义的启动监听类,SpringBoot启动时,每一个阶段都会调用我们自定义类的方法。

启动监听

下面我们看看SpringBoot是如何调用启动监听对应的方法的,我们现在以listeners.starting();方法调用为例,只要弄明白这个方法的调用,其他阶段也是依次类推。代码如下

    class SpringApplicationRunListeners {private final Log log;private final List<SpringApplicationRunListener> listeners;SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {this.log = log;this.listeners = new ArrayList<>(listeners);}void starting() {Iterator<SpringApplicationRunListener> it = this.listeners.iterator();//遍历调用所有的启动监听器while(it.hasNext()) {SpringApplicationRunListener listener = it.next();listener.starting();}}

代码很简单,SpringBoot用SpringApplicationRunListeners类把所有启动监听器都管理启动,通过他发布对应事件,然后循环去调用对应启动监听器的事件,而SpringBoot默认只要一个org.springframework.boot.context.event.EventPublishingRunListener启动监听器,通过他可以做很多很多的事情,下面我们会一步一步分析。

EventPublishingRunListener

在上一步实例化监听器的时候,会传入对应的SpringApplication对象,然后从该对象中,取出所有的监听器,这里的监听器是SpringApplication对象初始化的

    public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;//初始化一个简单应用事件广播器this.initialMulticaster = new SimpleApplicationEventMulticaster();Iterator<ApplicationListener> it = application.getListeners().iterator();//遍历所有的监听器,添加到广播器中while (it.hasNext()) {ApplicationListener<?> listener = (ApplicationListener) it.next();this.initialMulticaster.addApplicationListener(listener);}}

EventPublishingRunListener派发事件

EventPublishingRunListener派发正在开始事件,然后上一步我们添加的监听器就可以监听到事件触发,执行对应逻辑。

    public void starting() {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}

广播器继续调用广播事件的方法org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent

    /*** 广播事件* @param event 事件本身* @param eventType 事件类型*/public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);//获取一个执行器,这里应该是null的Executor executor = this.getTaskExecutor();//获取匹配到的监听器,不同版本可能有差异Iterator<ApplicationListener> it = this.getApplicationListeners(event, type).iterator();while(it.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)it.next();if (executor != null) {executor.execute(() -> {this.invokeListener(listener, event);});} else {//调用监听器this.invokeListener(listener, event);}}}

我们再来看看寻找监听器列表过程,因为SpringBoot初始化的是,给了我们很多监听器,但是并不一定每个监听器都支持该事件的响应,我们需要过滤出支持正在开始事件的监听器。调用的是 this.getApplicationListeners(event, type)方法,上一步的

    protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {Object source = event.getSource();Class<?> sourceType = source != null ? source.getClass() : null;//以事件类型和事件源构建一个keyListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);CachedListenerRetriever newRetriever = null;CachedListenerRetriever existingRetriever = (CachedListenerRetriever)this.retrieverCache.get(cacheKey);if (existingRetriever == null && (this.beanClassLoader == null || ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {//不存在会构建一个取回器,放到缓存中,newRetriever 会传到retrieveApplicationListeners方法里面赋很多值newRetriever = new CachedListenerRetriever();existingRetriever = (CachedListenerRetriever)this.retrieverCache.putIfAbsent(cacheKey, newRetriever);if (existingRetriever != null) {newRetriever = null;}}//存在的话直接返回监听器列表if (existingRetriever != null) {Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();if (result != null) {return result;}}return this.retrieveApplicationListeners(eventType, sourceType, newRetriever);}

这个方法非常简单,就是根据事件类型和事件源的类型生成一个缓存的KEY,如果没有就新增重新添加进去。这里我们是第一次进这个方法,应该是没有,代码来到this.retrieveApplicationListeners(eventType, sourceType, newRetriever);取回事件监听器

   /*** 取回事件监听器* @param eventType 事件类型* @param sourceType 事件源类型* @param retriever 取回者,监听器会在里面* @return 返回监听列表*/private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable AbstractApplicationEventMulticaster.CachedListenerRetriever retriever) {List<ApplicationListener<?>> allListeners = new ArrayList<>();Set<ApplicationListener<?>> filteredListeners = retriever != null ? new LinkedHashSet<>() : null;Set<String> filteredListenerBeans = retriever != null ? new LinkedHashSet<>() : null;LinkedHashSet listeners;LinkedHashSet listenerBeans;synchronized(this.defaultRetriever) {//取出所有的监听器listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}Iterator<ApplicationListener> it = listeners.iterator();while(it.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)it.next();//判断是否支持该事件的监听器if (this.supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {filteredListeners.add(listener);}allListeners.add(listener);}}//省略调不重要的代码。。。。//.....................AnnotationAwareOrderComparator.sort(allListeners);//放回结果return allListeners;}

我们再来看看this.supportsEvent(listener, eventType, sourceType)方法它是如何判断是否支持的,源码如下:

   /*** 是否支持事件* @param listener* @param eventType* @param sourceType* @return*/protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {//判断该监听器是否是GenericApplicationListener类型,如果不是需要用GenericApplicationListenerAdapter保证GenericApplicationListener smartListener = listener instanceof GenericApplicationListener ? (GenericApplicationListener)listener : new GenericApplicationListenerAdapter(listener);//调用 GenericApplicationListener的supportsEventType和supportsSourceType判断return ((GenericApplicationListener)smartListener).supportsEventType(eventType) && ((GenericApplicationListener)smartListener).supportsSourceType(sourceType);}

上述代码注释已经写的很清楚了,GenericApplicationListener 类型的监听器,子类会实现对应的两个方法,如果不是GenericApplicationListener ,需要使用适配类包装一下,然后使用监听器的泛型类型去判断(个人猜想),这里代码有点底层,有时间再去研究研究。

###调用监听器
匹配到的监听器我们已经找到了,现在就是循环遍历所有监听器,调用的处理事件,方法如下:

 this.invokeListener(listener, event);

最后调用用实际的监听器的处理

   private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {listener.onApplicationEvent(event);} catch (ClassCastException var6) {String msg = var6.getMessage();if (msg != null && !this.matchesClassCastMessage(msg, event.getClass())) {throw var6;}Log logger = LogFactory.getLog(this.getClass());if (logger.isTraceEnabled()) {logger.trace("Non-matching event type for listener: " + listener, var6);}}}

在发布ApplicationStartingEvent事件的时候,我们通过debuger知道符合条件的只有4个监听器,真正做事只有两个监听器,剩下两个事件的处理方法是空的。

  • LoggingApplicationListener: 完成日志系统的监听器

  • BackgroundPreinitializer: 执行预初始化,使用多线程新增了五个对象

  • CharsetInitializer: 字符集相关初始化

  • ValidationInitializer: 校验相关的初始化器

  • MessageConverterInitializer: 信息转换的初始化器

  • JacksonInitializer: json相关的初始化器

  • ConversionServiceInitializer: 对象相关服务初始化器

##总结
本节主要讲解监听器的发布事件,还有触发事件的处理逻辑,详情步骤如下:
run方法中SpringApplicationRunListeners#starting => 遍历SpringApplicationRunListener#starting=> 主要是EventPublishingRunListener#multicastEven这对象广发事件 => 寻找适合该事件的监听器 => 调用各个监听器的处理方法 => 结束
SpringApplicationRunListener 接口中可以看出,总共是七大事件,目前我们这里介绍了第一个事件的处理逻辑,剩下的事件处理大致一样,不会在细讲,最多讲一下各个监听器处理内容。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于JAVA+SpringBoot+Vue的景区民宿预约系统

基于JAVASpringBootVue的景区民宿预约系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; 哈…

Mamba所需的causal-conv1d 和mamba-ssm库在哪下载?

背景介绍 参照 Mamba [state-spaces/mamba: Mamba SSM architecture (github.com)] github中提到的环境安装[Installation 一栏] [Option] pip install causal-conv1d>1.4.0: an efficient implementation of a simple causal Conv1d layer used inside the Mamba block.…

浙版传媒思迈特软件大数据分析管理平台建设项目正式启动

近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大数据分析管理平台建设项目。浙版传媒相…

华为HarmonyOS灵活高效的消息推送服务(Push Kit) - 2 开通推送服务与配置Client ID

在开通推送服务前&#xff0c;请先参考“应用开发准备”完成基本准备工作&#xff0c;再继续进行以下开发活动。 说明 从HarmonyOS NEXT Developer Beta2起&#xff0c;开发者无需配置公钥指纹和Client ID。 操作步骤 登录AppGallery Connect网站&#xff0c;选择“我的项目…

UML图中部署图例题

答案&#xff1a;B 知识点&#xff1a; 组件图 一组构件之间的组织和依赖&#xff0c;专注于系统的静态实现视图 部署图 运行处理结点以及构件的配置&#xff0c;给出体系结构的静态视图 类图 一组对象&#xff0c;接口&#xff0c;协作和它们之间的关系 UML图中涉及到…

ALTIUM DESIGNER PCB设计中关闭和打开捕捉热点(hot spot)功能

ALTIUM DESIGNER PCB设计中关闭和打开捕捉热点&#xff08;snap to hot spot&#xff09;功能 在采用ALTIUM DESIGNER 18 进行PCB元器件布局时&#xff0c;我喜欢将元器件放置在栅格&#xff08;grid&#xff09;上&#xff0c;这样元器件的位置比较规整。但在设置完栅格后&am…

Java流程控制语句——跳转语句详解:break 与 continue 有什么区别?

&#x1f310;在Java编程中&#xff0c;break和continue是两个重要的控制流语句&#xff0c;它们允许开发者根据特定条件改变程序的执行流程。虽然两者都用于中断当前的行为&#xff0c;但它们的作用方式不同。本文将通过生动的例子来详细解释这两个语句&#xff0c;并使用流程…

VMware启动时报错: “另一个程序已锁定文件的一部分,进程无法访问” 分析记录

项目场景&#xff1a; VMware启动时报错: “另一个程序已锁定文件的一部分,进程无法访问” 问题描述 VMware启动时报错: “另一个程序已锁定文件的一部分,进程无法访问” 原因分析&#xff1a; 虚拟机开启后会对部分文件继续加密&#xff0c;关闭时虚拟机会自动对其解密&…

计算机毕业设计之:基于uni-app的校园活动信息共享系统设计与实现(三端开发,安卓前端+网站前端+网站后端)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

Transcipher:从对称加密到同态加密

摘要 本文介绍了Transcipher的概念。在Transcipher的框架下&#xff0c;用户使用高效的对称加密&#xff0c;对自己的数据进行加密&#xff0c;然后将密文和私钥的同态加密密文传输给服务器。服务器进行同态解密&#xff0c;得到用户数据同态加密的密文。Transcipher通过将计算…

分布式锁的几种方案对比?你了解多少种呢?

目录标题 1.关于分布式锁2.分布式锁的实现方案2.1 基于数据库实现2.1.1乐观锁的实现方式2.1.2 悲观锁的实现方式2.1.3 数据库锁的优缺点 2.2 基于Redis实现2.2.1 基于缓存实现分布式锁2.2.2缓存实现分布式锁的优缺点 2.3 基于Zookeeper实现2.3.1 如何实现&#xff1f;2.3.2 zk实…

1.量化第一步,搭建属于自己的金融数据库!

数据是一切量化研究的前提。 做量化没有数据&#xff0c;就相当于做饭时没有食材。 很多时候&#xff0c;我们需要从大量的数据中寻找规律&#xff0c;并从中开发出策略。如果我们每次使用的时候&#xff0c;都从网上去找数据&#xff0c;一方面效率低下&#xff0c;另一方面短…

运行 xxxxApplication 时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行,然后重新运行。

一、问题描述 运行 xxxxApplication 时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行&#xff0c;然后重新运行。 二、问题分析 在idea中&#xff0c;运行一个springboot项目&#xff0c;在使用大量的库和依赖的时候&#xff0c;会出现报错“命令行过长”&…

你了解system V的ipc底层如何设计的吗?消息队列互相通信的原理是什么呢?是否经常将信号量和信号混淆呢?——问题详解

前言&#xff1a;本节主要讲解消息队列&#xff0c; 信号量的相关知识。 ——博主主要是以能够理解为目的进行讲解&#xff0c; 所以对于接口的使用或者底层原理很少涉及。 主要的讲解思路就是先讨论消息队列的原理&#xff0c; 提一下接口。 然后讲解ipc的设计——这个设计一些…

构建未来企业的理论基石:业务能力建模指南的深度解析与战略实施框架

数字化转型已经成为全球企业的战略焦点&#xff0c;在这个过程中&#xff0c;如何有效地将复杂的业务需求、技术架构和市场变化结合&#xff0c;形成具备长期竞争力的企业能力框架&#xff0c;是企业成败的关键。《业务能力指南》提供了一套经过验证的理论体系&#xff0c;帮助…

【番茄成熟度数据集】12类names

【番茄成熟度数据集】12类 names: [half-ripe, ripe, rotten tomatoes, tomato fully ripe, tomato semi ripe, tomato unripe, tomato_half_ripe, tomato_overripe, tomato_ripe, tomato_rotten, tomato_unripe, unripe] 名称: [半熟的, 成熟的, 腐烂的西红柿, 西红柿完全成熟…

centos7离线安装MySQL8

下载Mysql安装包地址 https://dev.mysql.com/downloads/mysql/解压到指定目录 [rootlocalhost tools]# tar -xvf mysql-8.4.2-1.el7.x86_64.rpm-bundle.tar -C /root/training [rootlocalhost tools]# cd ../training/ [rootlocalhost training]# ll total 1027204 -rw-r--r-…

计算机毕业设计之:基于微信小程序的诗词智能学习系统(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

Spring_AMQP

文章目录 一、SpringAMQP二、SpringAMQP应用2.1、消息发送2.2、消息接收 一、SpringAMQP SpringAMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp的官方地址。 SpringAMQP提供了三个功能&am…

PMP--二模--解题--51-60

文章目录 14.敏捷--术语表--完成的定义DoD--它是团队需要满足的所有标准的核对单&#xff0c;只有可交付成果满足该核对单才能视为准备就绪可供客户使用。51、 [单选] 在冲刺计划会议上&#xff0c;Scrum主管重申&#xff0c;如果在冲刺结束时敏捷项目团队正在构建的产品增量没…