springboot启动读取yml原理,springboot 读取yml参数

  springboot启动读取yml原理,springboot 读取yml参数

  00-1010后台加载监听器执行run方法加载配置文件封装节点调用构造函数

  00-1010前几天项目里有一个需求,需要一个开关来控制代码里是否执行了一段逻辑。所以,当然,在yml文件中配置了一个属性作为开关,有了nacos,这个值可以随时更改以达到我们的目的。yml文件如下所示:

  switch: turnOn: on程序中的代码也很简单。大致逻辑如下。如果采集的开关字段为on,则if判断中的代码将被执行,否则不会被执行:

  @Value(${switch.turnOn} )上的私有字符串;@ get mapping( testn )public void test(){ if( on )。equals (on)) {//todo}}但是当代码实际运行时,我们发现判断中的代码并不会一直执行。直到我们调试之后才发现,这里得到的值其实不是on而是true。

  看到这里,是不是觉得有点意思?首先,瞎猜的是在解析yml的过程中on被当作特殊值,所以我简单的多测试了几个例子,将yml中的属性扩展为如下:

  开关:打开3360打开关闭3360关闭打开2:“打开”关闭2:“关闭”并再次执行代码以查看映射值:

  可以看到,yml中不带引号的on和off转换为true和false,带引号的则保持原来的值不变。

  在这里,我不禁好奇,为什么会这样?于是我忍着睡意,翻了翻源代码,猛批SpringBoot加载yml配置文件的过程。终于看出了一些门道。让我们一点一点地来阐述它们吧!

  因为配置文件的加载会涉及到一些关于SpringBoot启动的知识,不熟悉这一块的同学可以看一篇古代九头蛇写的文章来热身。在下面的介绍中,只选取加载和解析配置文件的一些重要步骤进行分析,其他不相关的部分就省略了。

  

目录
当我们启动一个SpringBoot程序,执行SpringApplication.run()时,首先在初始化SpringApplication的过程中,加载了11个实现ApplicationListener接口的拦截器。

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

  这里列出的10个加载在spring-boot中,剩下的一个加载在spring-boot-autoconfigure中。最关键的一个是ConfigFileApplicationListener,关系到后面要讲的配置文件的加载。

  

背景

spring application实例化后,接下来将执行其run方法。

  正如您所看到的,在这里通过getRunListeners方法获得的SpringApplicationRunListeners中,EventPublishingRunListener绑定了我们之前加载的11个侦听器。但是,当执行starting方法时,它会根据类型进行过滤。最终实际执行的只是四个监听器的onApplicationEvent方法,并没有我们想要看到的ConfigFileApplicationListener。我们往下看。

  p style="text-align:center">

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

  

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

  

  

加载配置文件

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

  

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

  

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

  

  

封装Node

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

  

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

  

  在解析yml的过程中实际使用了Composer构建器来生成节点,在它的getNode方法中,通过解析器事件来创建节点。通常来说,它会将yml中的一组数据封装成一个MappingNode节点,它的内部实际上是一个NodeTuple组成的ListNodeTupleMap的结构类似,由一对对应的keyNodevalueNode构成,结构如下:

  

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

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

  

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

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

  

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

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

  

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

  到这里,我们就解释清楚了ScalarNodetag属性究竟是怎么获取到的了,之后方法调用层层返回,返回到Origi

  

  

调用构造器

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

  nTrackingConstructor父类BaseConstructorgetData方法中。接下来,继续执行constructDocument方法,完成对yml文档的解析。

  

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

  

  当tagbool类型时,会找到SafeConstruct中的内部类ConstructYamlBool作为构造器,并调用它的construct方法实例化一个对象,来作为ScalarNode节点的value的值:

  

  在construct方法中,取到的val就是之前的on,至于下面的这个BOOL_VALUES,也是提前初始化好的一个HashMap,里面提前存放了一些对应的映射关系,key是下面列出的这些关键字,value则是Boolean类型的truefalse

  

  到这里,yml中的属性解析流程就基本完成了,我们也明白了为什么yml中的on会被转化为true的原理了。

  

  

思考

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

  

sw.turnOn=onsw.turnOff=off
执行一下程序,看一下结果:

  

  可以看到,使用properties配置文件能够正常读取结果,看来是在解析的过程中没有做特殊处理,至于解析的过程,有兴趣的小伙伴可以自己去阅读一下源码。

  到此这篇关于SpringBoot解析yml全流程详解的文章就介绍到这了,更多相关SpringBoot解析yml 内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: