【Spring Boot】Spring—加载监听器

这篇具有很好参考价值的文章主要介绍了【Spring Boot】Spring—加载监听器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

前几天的时候,项目里有一个需求,需要一个开关控制代码中是否执行一段逻辑,于是理所当然的在yml文件中配置了一个属性作为开关,再配合nacos就可以随时改变这个值达到我们的目的,yml文件中是这样写的:

switch:
  turnOn: on

程序中的代码也很简单,大致的逻辑就是下面这样,如果取到的开关字段是on的话,那么就执行if判断中的代码,否则就不执行:

@Value("${switch.turnOn}")
private String on;
​
@GetMapping("testn")
public void test(){
    if ("on".equals(on)){
        //TODO
    }
}

但是当代码实际跑起来,有意思的地方来了,我们发现判断中的代码一直不会被执行,直到debug一下,才发现这里的取到的值居然不是on而是true。
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

看到这,是不是感觉有点意思,首先盲猜是在解析yml的过程中把on作为一个特殊的值进行了处理,于是我干脆再多测试了几个例子,把yml中的属性扩展到下面这些:

switch:
  turnOn: on
  turnOff: off
  turnOn2: 'on'
  turnOff2: 'off'

再执行一下代码,看一下映射后的值:
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

可以看到,yml中没有带引号的on和off被转换成了true和false,带引号的则保持了原来的值不发生改变。

到这里,让我忍不住有点好奇,为什么会发生这种现象呢?于是强忍着困意翻了翻源码,硬磕了一下SpringBoot加载yml配置文件的过程,终于让我看出了点门道,下面我们一点一点细说!

因为配置文件的加载会涉及到一些SpringBoot启动的相关知识,所以如果对SpringBoot启动不是很熟悉的同学,可以先提前先看一下Hydra在古早时期写过一篇Spring Boot零配置启动原理预热一下。下面的介绍中,只会摘出一些对加载和解析配置文件比较重要的步骤进行分析,对其他无关部分进行了省略。

加载监听器

当我们启动一个SpringBoot程序,在执行SpringApplication.run()的时候,首先在初始化SpringApplication的过程中,加载了11个实现了ApplicationListener接口的拦截器。
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

这11个自动加载的ApplicationListener,是在spring.factories中定义并通过SPI扩展被加载的:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

这里列出的10个是在spring-boot中加载的,还有剩余的1个是在spring-boot-autoconfigure中加载的。其中最关键的就是ConfigFileApplicationListener,它和后面要讲到的配置文件的加载相关。

执行run方法

在实例化完成SpringApplication后,会接着往下执行它的run方法。

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

可以看到,这里通过getRunListeners方法获取的SpringApplicationRunListeners中,EventPublishingRunListener绑定了我们前面加载的11个监听器。但是在执行starting方法时,根据类型进行了过滤,最终实际只执行了4个监听器的onApplicationEvent方法,并没有我们希望看到的ConfigFileApplicationListener,让我们接着往下看。

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

当run方法执行到prepareEnvironment时,会创建一个ApplicationEnvironmentPreparedEvent类型的事件,并广播出去。这时所有的监听器中,有7个会监听到这个事件,之后会分别调用它们的onApplicationEvent方法,其中就有了我们心心念念的ConfigFileApplicationListener,接下来让我们看看它的onApplicationEvent方法中做了什么。

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在方法的调用过程中,会加载系统自己的4个后置处理器以及ConfigFileApplicationListener自身,一共5个后置处理器,并执行他们的postProcessEnvironment方法,其他4个对我们不重要可以略过,最终比较关键的步骤是创建Loader实例并调用它的load方法。

加载配置文件

    这里的Loader是ConfigFileApplicationListener的一个内部类,看一下Loader对象实例化的过程:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在实例化Loader对象的过程中,再次通过SPI扩展的方式加载了两个属性文件加载器,其中的YamlPropertySourceLoader就和后面的yml文件的加载、解析密切关联,而另一个PropertiesPropertySourceLoader则负责properties文件的加载。创建完Loader实例后,接下来会调用它的load方法。

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在load方法中,会通过嵌套循环方式遍历默认配置文件存放路径,再加上默认的配置文件名称、以及不同配置文件加载器对应解析的后缀名,最终找到我们的yml配置文件。接下来,开始执行loadForFileExtension方法。

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在loadForFileExtension方法中,首先将classpath:/application.yml加载为Resource文件,接下来准备正式开始,调用了之前创建好的YamlPropertySourceLoader对象的load方法。

封装Node

    在load方法中,开始准备进行配置文件的解析与数据封装:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

load方法中调用了OriginTrackedYmlLoader对象的load方法,从字面意思上我们也可以理解,它的用途是原始追踪yml的加载器。中间一连串的方法调用可以忽略,直接看最后也是最重要的是一步,调用OriginTrackingConstructor对象的getData接口,来解析yml并封装成对象。

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在解析yml的过程中实际使用了Composer构建器来生成节点,在它的getNode方法中,通过解析器事件来创建节点。通常来说,它会将yml中的一组数据封装成一个MappingNode节点,它的内部实际上是一个NodeTuple组成的List,NodeTuple和Map的结构类似,由一对对应的keyNode和valueNode构成,结构如下:
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

好了,让我们再回到上面的那张方法调用流程图,它是根据文章开头的yml文件中实际内容内容绘制的,如果内容不同调用流程会发生改变,大家只需要明白这个原理,下面我们具体分析。

首先,创建一个MappingNode节点,并将switch封装成keyNode,然后再创建一个MappingNode,作为外层MappingNode的valueNode,同时存储它下面的4组属性,这也是为什么上面会出现4次循环的原因。如果有点困惑也没关系,看一下下面的这张图,就能一目了然了解它的结构。
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在上图中,又引入了一种新的ScalarNode节点,它的用途也比较简单,简单String类型的字符串用它来封装成节点就可以了。到这里,yml中的数据被解析完成并完成了初步的封装,可能眼尖的小伙伴要问了,上面这张图中为什么在ScalarNode中,除了value还有一个tag属性,这个属性是干什么的呢?

在介绍它的作用前,先说一下它是怎么被确定的。这一块的逻辑比较复杂,大家可以翻一下ScannerImpl类fetchMoreTokens方法的源码,这个方法会根据yml中每一个key或value是以什么开头,来决定以什么方式进行解析,其中就包括了{、[、'、%、?等特殊符号的情况。以解析不带任何特殊字符的字符串为例,简要的流程如下,省略了一些不重要部分:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在这张图的中间步骤中,创建了两个比较重要的对象ScalarToken和ScalarEvent,其中都有一个为true的plain属性,可以理解为这个属性是否需要解释,是后面获取Resolver的关键属性之一。

上图中的yamlImplicitResolvers其实是一个提前缓存好的HashMap,已经提前存储好了一些Char类型字符与ResolverTuple的对应关系:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

当解析到属性on时,取出首字母o对应的ResolverTuple,其中的tag就是tag:yaml.org.2002:bool。当然了,这里也不是简单的取出就完事了,后续还会对属性进行正则表达式的匹配,看与regexp中的值是否能对的上,检查无误时才会返回这个tag。

到这里,我们就解释清楚了ScalarNode中tag属性究竟是怎么获取到的了,之后方法调用层层返回,返回到OriginTrackingConstructor父类BaseConstructor的getData方法中。接下来,继续执行constructDocument方法,完成对yml文档的解析。

调用构造器

    在constructDocument中,有两步比较重要,第一步是推断当前节点应该使用哪种类型的构造器,第二步是使用获得的构造器来重新对Node节点中的value进行赋值,简易流程如下,省去了循环遍历的部分:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

推断构造器种类的过程也很简单,在父类BaseConstructor中,缓存了一个HashMap,存放了节点的tag类型到对应构造器的映射关系。在getConstructor方法中,就使用之前节点中存入的tag属性来获得具体要使用的构造器:
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

当tag为bool类型时,会找到SafeConstruct中的内部类 ConstructYamlBool作为构造器,并调用它的construct方法实例化一个对象,来作为ScalarNode节点的value的值:
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

在construct方法中,取到的val就是之前的on,至于下面的这个BOOL_VALUES,也是提前初始化好的一个HashMap,里面提前存放了一些对应的映射关系,key是下面列出的这些关键字,value则是Boolean类型的true或false:
【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

到这里,yml中的属性解析流程就基本完成了,我们也明白了为什么yml中的on会被转化为true的原理了。至于最后,Boolean类型的true或false是如何被转化为的字符串,就是@Value注解去实现的了。

思考

    那么,下一个问题来了,既然yml文件解析中会做这样的特殊处理,那么如果换成properties配置文件怎么样呢?
sw.turnOn=on
sw.turnOff=off

执行一下程序,看一下结果:

【Spring Boot】Spring—加载监听器,external,spring boot,spring,java

可以看到,使用properties配置文件能够正常读取结果,看来是在解析的过程中没有做特殊处理,至于解析的过程,有兴趣的小伙伴可以自己去阅读一下源码。文章来源地址https://www.toymoban.com/news/detail-716670.html

到了这里,关于【Spring Boot】Spring—加载监听器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring高手之路7——事件机制与监听器的全面探索

      观察者模式是一种行为设计模式,它定义了对象之间的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。在这个模式中,改变状态的对象被称为主题,依赖的对象被称为观察者。 举个实际的例子: 事件源(Event Source) :可以视

    2024年02月11日
    浏览(30)
  • Spring监听器用法与原理详解(带ApplicationListener模型图)

    相信大家都或多或少知道Spring中的监听器,有些人还能说出它采用了 观察者模式 ,但其实它还用到了 适配器模式 , 工厂模式 等。当然,仍有不少人是完全不了解Spring的监听及其机制的,本次我们就来深入学习一下 Spring监听器 Spring监听器是一种 特殊的类,它们能帮助开发

    2024年02月06日
    浏览(42)
  • Spring项目配置文件中RabbitMQ监听器各个参数的作用

    spring.rabbitmq.listener.simple.concurrency :设置监听器容器的并发消费者数量,默认为1,即单线程消费。 spring.rabbitmq.listener.simple.max-concurrency :设置监听器容器的最大并发消费者数量。 spring.rabbitmq.listener.simple.prefetch :设置每个消费者从RabbitMQ服务器获取的消息数量,即每次从队列

    2024年02月16日
    浏览(26)
  • 【Spring Boot系列】- Spring Boot侦听器Listener

    什么事Web监听器?web监听器就是 Servlet中特殊的类 ,他们能帮助开发者监听web中的特定事件,比如ServletContext、HttpSession、ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控等等。web监听器的使用场景有很多;Spring的监听器是一种

    2024年02月07日
    浏览(31)
  • HttpSessionListener监听器和HttpSessionAttributeListener监听器

    1.作用:监听Session创建或销毁,即生命周期监听 2.相关方法: 3.使用场景: 和前面的ServletContextListener等一样,可以用于监控用户上线和离线 4.代码 HttpSessionListener监听器 实现类 HttpSessionAttributeListener监听器 1.作用:监听Session属性的变化,使用少 2.相关方法: 3.代码 监听器 实

    2024年02月04日
    浏览(36)
  • Listener监听器----HttpServletRequest对象的生命周期监听器

    一、HttpServletRequest对象的生命周期监听器         ServletRequestListener接口定义了ServletRequest(是HttpServletRequest接口的父接口类型)对象生命周期的监听行为。 void requestInitialized(ServletRequestEvent sre)         HttpServletRequest对象创建后会触发该监听器方法,并将已创建HttpServletR

    2024年01月23日
    浏览(46)
  • camunda执行监听器和任务监听器有什么区别

    Camunda的执行监听器和任务监听器是用于添加自定义逻辑的监听器,它们的区别在于作用对象和触发事件的不同。 执行监听器是与BPMN流程中的各种流程元素(例如开始事件、用户任务、服务任务、网关等)相关联的。执行监听器可以在流程元素执行前、执行后或抛出异常时添

    2024年02月04日
    浏览(36)
  • 消息监听器和消息监听容器

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 消息监听器顾名思义用来接收消息,它是使用消息监听容器的必须条件。目前有8个消息监听器: 使用自动提交或容器管理的提交方法之一,处理从 Kafka 消费者 p

    2024年02月07日
    浏览(37)
  • 【Spring Boot】Spring Boot自动加载机制:简化应用程序的启动

    在微服务盛行的今天,快速搭建和启动应用程序变得至关重要。Spring Boot作为Java生态系统中主流的框架,其自动加载机制使得开发者能够快速构建和启动应用程序。本文将详细介绍Spring Boot的自动加载机制,并通过代码示例加以说明。 首先,我们要了解Spring Boot自动加载机制

    2024年02月11日
    浏览(27)
  • Spring boot 实现监听 Redis key 失效事件

    方式一:修改配置文件 redis.conf 方式二:命令行开启 notify-keyspace-events 选项的默认值为空 notify-keyspace-events 的参数可以是以下字符的 任意组合 , 它指定了服务器该发送哪些类型的通知。 字符 发送的通知 K 键空间通知,所有通知以 keyspace@ 为前缀 E 键事件通知,所有通知以

    2024年02月20日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包