[Spring Boot] Spring—loading listener

Preface

A few days ago, there was a requirement in the project, which required a switch to control whether to execute a piece of logic in the code, so of course we configured an attribute in the yml file as a switch, and with nacos, we can change this value at any time to achieve our purpose. , this is written in the yml file:

switch:
  turnOn: on

The code in the program is also very simple. The general logic is as follows. If the obtained switch field is on, then the code in the if judgment will be executed, otherwise it will not be executed:

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

But when the code actually ran, the interesting part came. We found that the code in judgment would never be executed. It was not until we debugged that we found that the value obtained here was not on but true.
Please add image description

Seeing this, it seems a bit interesting. First of all, the blind guess is that on is treated as a special value in the process of parsing yml, so I simply tested a few more examples and extended the attributes in yml to the following These:

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

Execute the code again and take a look at the mapped value:
Please add image description

As you can see, the on and off without quotes in the yml are converted into true and false, while the quotes keep the original values ​​unchanged.

At this point, I can't help but be a little curious, why does this happen? So I endured the sleepiness and flipped through the source code, and struggled with the process of loading the yml configuration file in SpringBoot. Finally, I saw some clues. Let’s explain it in detail below!

Because the loading of the configuration file will involve some knowledge about Spring Boot startup, if you are not very familiar with Spring Boot startup, you can first read the Spring Boot zero-configuration startup principle written by Hydra in ancient times to warm up. . In the following introduction, only some important steps for loading and parsing configuration files will be extracted for analysis, and other irrelevant parts will be omitted.

Load listener

When we start a SpringBoot program and execute SpringApplication.run(), first of all, during the process of initializing SpringApplication, 11 interceptors that implement the ApplicationListener interface are loaded.
Please add image description

These 11 automatically loaded ApplicationListeners are defined in spring.factories and loaded through SPI extensions:

Please add image description

The 10 listed here are loaded in spring-boot, and the remaining 1 is loaded in spring-boot-autoconfigure. The most critical one is ConfigFileApplicationListener, which is related to the loading of the configuration file to be discussed later.

Execute run method

After the SpringApplication is instantiated, its run method will be executed.

Please add image description

As you can see, among the SpringApplicationRunListeners obtained through the getRunListeners method, the EventPublishingRunListener is bound to the 11 listeners we loaded earlier. However, when executing the starting method, filtering was performed based on the type. In the end, only the onApplicationEvent method of 4 listeners was actually executed, and there was no ConfigFileApplicationListener we expected to see. Let us continue to look down.

Please add image description

When the run method is executed to prepareEnvironment, an event of type ApplicationEnvironmentPreparedEvent will be created and broadcasted. At this time, 7 of all the listeners will listen to this event, and then their onApplicationEvent methods will be called respectively. Among them is the ConfigFileApplicationListener that we are thinking about. Next, let us take a look at what is done in its onApplicationEvent method.

Please add image description

During the calling process of the method, the system's own 4 post-processors and the ConfigFileApplicationListener itself will be loaded, a total of 5 post-processors, and their postProcessEnvironment methods will be executed. The other 4 are not important to us and can be skipped. Final comparison The key step is to create a Loader instance and call its load method.

Load configuration file

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

Please add image description

In the process of instantiating the Loader object, two property file loaders are loaded again through SPI extension. The YamlPropertySourceLoader is closely related to the loading and parsing of the subsequent yml file, while the other PropertiesPropertySourceLoader is responsible for loading the properties file. . After creating the Loader instance, its load method will be called.

Please add image description

In the load method, the default configuration file storage path will be traversed through a nested loop, plus the default configuration file name and the corresponding suffix names parsed by different configuration file loaders, and finally our yml configuration file will be found. Next, start executing the loadForFileExtension method.

Please add image description

In the loadForFileExtension method, classpath:/application.yml is first loaded as a Resource file, and then the preparation is officially started by calling the load method of the previously created YamlPropertySourceLoader object.

Encapsulate Node

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

Please add image description

The load method calls the load method of the OriginTrackedYmlLoader object. From the literal meaning, we can also understand that its purpose is the loader of the original tracking yml. The series of method calls in the middle can be ignored. Let's look directly at the last and most important step. Call the getData interface of the OriginTrackingConstructor object to parse the yml and encapsulate it into an object.

Please add image description

In the process of parsing yml, the Composer builder is actually used to generate nodes. In its getNode method, nodes are created through parser events. Generally speaking, it encapsulates a set of data in yml into a MappingNode node. Its interior is actually a List composed of NodeTuple. The structure of NodeTuple is similar to Map, consisting of a pair of corresponding keyNode and valueNode. The structure is as follows :
Please add image description

Okay, let's go back to the method calling flow chart above. It is drawn based on the actual content in the yml file at the beginning of the article. If the content is different, the calling process will change. You only need to understand this principle. Next we Specific analysis.

First, create a MappingNode node, encapsulate the switch into a keyNode, and then create a MappingNode as the valueNode of the outer MappingNode and store the 4 sets of attributes below it. This is why there are 4 loops above. It’s okay if you’re a little confused, take a look at the picture below and you’ll be able to understand its structure at a glance.
Please add image description

In the figure above, a new ScalarNode node is introduced. Its purpose is relatively simple. Simple String type strings can be encapsulated into nodes with it. At this point, the data in the yml has been parsed and preliminary encapsulation has been completed. Sharp-eyed friends may want to ask, why in the above picture, in the ScalarNode, in addition to value, there is a tag attribute. What is this attribute used for?

Before introducing its function, let’s first talk about how it is determined. The logic of this piece is relatively complicated. You can look at the source code of the fetchMoreTokens method of the ScannerImpl class. This method will determine how to parse based on what each key or value in the yml begins with, including {, [, ', %, ? and other special symbols. Taking parsing a string without any special characters as an example, the brief process is as follows, with some unimportant parts omitted:

Please add image description

In the middle step of this picture, two more important objects, ScalarToken and ScalarEvent, are created, both of which have a plain attribute of true. It can be understood that whether this attribute requires explanation is one of the key attributes for obtaining the Resolver later.

The yamlImplicitResolvers in the picture above is actually a HashMap that has been cached in advance. The correspondence between some Char type characters and ResolverTuple has been stored in advance:

Please add image description

When the attribute on is parsed, the ResolverTuple corresponding to the first letter o is taken out, and the tag is tag:yaml.org.2002:bool. Of course, it is not just a matter of taking it out. The attribute will then be matched with a regular expression to see if it matches the value in the regexp. The tag will be returned only when the check is correct.

At this point, we have clearly explained how the tag attribute in ScalarNode is obtained, and then the method calls are returned layer by layer, returning to the getData method of the OriginTrackingConstructor parent class BaseConstructor. Next, continue to execute the constructDocument method to complete the parsing of the yml document.

call constructor

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

Please add image description

The process of inferring the type of constructor is also very simple. In the parent class BaseConstructor, a HashMap is cached, which stores the mapping relationship between the tag type of the node and the corresponding constructor. In the getConstructor method, use the tag attribute stored in the previous node to obtain the specific constructor to be used:
Please add image description

When the tag is of bool type, the internal class ConstructYamlBool in SafeConstruct will be found as the constructor, and its construct method will be called to instantiate an object as the value of the ScalarNode node:
Please add image description

In the construct method, the obtained val is the previous on. As for the BOOL_VALUES below, it is also a HashMap initialized in advance. Some corresponding mapping relationships are stored in advance. The key is the keywords listed below, and the value is Is of type Boolean true or false:
Please add image description

At this point, the attribute parsing process in yml is basically completed, and we also understand the principle of why on in yml is converted to true. As for the final step, how the true or false of the Boolean type is converted into a string is implemented by the @Value annotation.

think

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

Execute the program and see the results:

Please add image description

It can be seen that the results can be read normally using the properties configuration file. It seems that no special processing is done during the parsing process. As for the parsing process, interested friends can read the source code themselves.

Guess you like

Origin blog.csdn.net/2202_75623950/article/details/132942968