偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

18張圖,詳解SpringBoot解析yml全流程

開(kāi)發(fā) 后端
前幾天的時(shí)候,項(xiàng)目里有一個(gè)需求,需要一個(gè)開(kāi)關(guān)控制代碼中是否執(zhí)行一段邏輯,于是理所當(dāng)然的在yml文件中配置了一個(gè)屬性作為開(kāi)關(guān),再配合nacos就可以隨時(shí)改變這個(gè)值達(dá)到我們的目的。

 背景

前幾天的時(shí)候,項(xiàng)目里有一個(gè)需求,需要一個(gè)開(kāi)關(guān)控制代碼中是否執(zhí)行一段邏輯,于是理所當(dāng)然的在yml文件中配置了一個(gè)屬性作為開(kāi)關(guān),再配合nacos就可以隨時(shí)改變這個(gè)值達(dá)到我們的目的,yml文件中是這樣寫(xiě)的: 

  1. switch:  
  2.   turnOn: on 

程序中的代碼也很簡(jiǎn)單,大致的邏輯就是下面這樣,如果取到的開(kāi)關(guān)字段是on的話,那么就執(zhí)行if判斷中的代碼,否則就不執(zhí)行: 

  1. @Value("${switch.turnOn}")  
  2. private String on;  
  3. @GetMapping("testn")  
  4. public void test(){  
  5.     if ("on".equals(on)){  
  6.         //TODO  
  7.     }  

但是當(dāng)代碼實(shí)際跑起來(lái),有意思的地方來(lái)了,我們發(fā)現(xiàn)判斷中的代碼一直不會(huì)被執(zhí)行,直到debug一下,才發(fā)現(xiàn)這里的取到的值居然不是on而是true。

看到這,是不是感覺(jué)有點(diǎn)意思,首先盲猜是在解析yml的過(guò)程中把on作為一個(gè)特殊的值進(jìn)行了處理,于是我干脆再多測(cè)試了幾個(gè)例子,把yml中的屬性擴(kuò)展到下面這些: 

  1. switch:  
  2.   turnOn: on  
  3.   turnOff: off  
  4.   turnOn2: 'on'  
  5.   turnOff2: 'off' 

再執(zhí)行一下代碼,看一下映射后的值:

可以看到,yml中沒(méi)有帶引號(hào)的on和off被轉(zhuǎn)換成了true和false,帶引號(hào)的則保持了原來(lái)的值不發(fā)生改變。

到這里,讓我忍不住有點(diǎn)好奇,為什么會(huì)發(fā)生這種現(xiàn)象呢?于是強(qiáng)忍著困意翻了翻源碼,硬磕了一下SpringBoot加載yml配置文件的過(guò)程,終于讓我看出了點(diǎn)門(mén)道,下面我們一點(diǎn)一點(diǎn)細(xì)說(shuō)!

因?yàn)榕渲梦募募虞d會(huì)涉及到一些SpringBoot啟動(dòng)的相關(guān)知識(shí),所以如果對(duì)這一塊不是很熟悉的同學(xué),可以先提前先看一下Hydra在古早時(shí)期寫(xiě)過(guò)一篇文章預(yù)熱一下。下面的介紹中,只會(huì)摘出一些對(duì)加載和解析配置文件比較重要的步驟進(jìn)行分析,對(duì)其他無(wú)關(guān)部分進(jìn)行了省略。

加載監(jiān)聽(tīng)器

當(dāng)我們啟動(dòng)一個(gè)SpringBoot程序,在執(zhí)行SpringApplication.run()的時(shí)候,首先在初始化SpringApplication的過(guò)程中,加載了11個(gè)實(shí)現(xiàn)了ApplicationListener接口的攔截器。

這11個(gè)自動(dòng)加載的ApplicationListener,是在spring.factories中定義并通過(guò)SPI擴(kuò)展被加載的:

這里列出的10個(gè)是在spring-boot中加載的,還有剩余的1個(gè)是在spring-boot-autoconfigure中加載的。其中最關(guān)鍵的就是ConfigFileApplicationListener,它和后面要講到的配置文件的加載相關(guān)。

執(zhí)行run方法

在實(shí)例化完成SpringApplication后,會(huì)接著往下執(zhí)行它的run方法。

可以看到,這里通過(guò)getRunListeners方法獲取的SpringApplicationRunListeners中,EventPublishingRunListener綁定了我們前面加載的11個(gè)監(jiān)聽(tīng)器。但是在執(zhí)行starting方法時(shí),根據(jù)類型進(jìn)行了過(guò)濾,最終實(shí)際只執(zhí)行了4個(gè)監(jiān)聽(tīng)器的onApplicationEvent方法,并沒(méi)有我們希望看到的ConfigFileApplicationListener,讓我們接著往下看。

當(dāng)run方法執(zhí)行到prepareEnvironment時(shí),會(huì)創(chuàng)建一個(gè)ApplicationEnvironmentPreparedEvent類型的事件,并廣播出去。這時(shí)所有的監(jiān)聽(tīng)器中,有7個(gè)會(huì)監(jiān)聽(tīng)到這個(gè)事件,之后會(huì)分別調(diào)用它們的onApplicationEvent方法,其中就有了我們心心念念的ConfigFileApplicationListener,接下來(lái)讓我們看看它的onApplicationEvent方法中做了什么。

在方法的調(diào)用過(guò)程中,會(huì)加載系統(tǒng)自己的4個(gè)后置處理器以及ConfigFileApplicationListener自身,一共5個(gè)后置處理器,并執(zhí)行他們的postProcessEnvironment方法,其他4個(gè)對(duì)我們不重要可以略過(guò),最終比較關(guān)鍵的步驟是創(chuàng)建Loader實(shí)例并調(diào)用它的load方法。

加載配置文件

這里的Loader是ConfigFileApplicationListener的一個(gè)內(nèi)部類,看一下Loader對(duì)象實(shí)例化的過(guò)程:

在實(shí)例化Loader對(duì)象的過(guò)程中,再次通過(guò)SPI擴(kuò)展的方式加載了兩個(gè)屬性文件加載器,其中的YamlPropertySourceLoader就和后面的yml文件的加載、解析密切關(guān)聯(lián),而另一個(gè)PropertiesPropertySourceLoader則負(fù)責(zé)properties文件的加載。創(chuàng)建完Loader實(shí)例后,接下來(lái)會(huì)調(diào)用它的load方法。

在load方法中,會(huì)通過(guò)嵌套循環(huán)方式遍歷默認(rèn)配置文件存放路徑,再加上默認(rèn)的配置文件名稱、以及不同配置文件加載器對(duì)應(yīng)解析的后綴名,最終找到我們的yml配置文件。接下來(lái),開(kāi)始執(zhí)行l(wèi)oadForFileExtension方法。

在loadForFileExtension方法中,首先將classpath:/application.yml加載為Resource文件,接下來(lái)準(zhǔn)備正式開(kāi)始,調(diào)用了之前創(chuàng)建好的YamlPropertySourceLoader對(duì)象的load方法。

封裝Node

在load方法中,開(kāi)始準(zhǔn)備進(jìn)行配置文件的解析與數(shù)據(jù)封裝:

load方法中調(diào)用了OriginTrackedYmlLoader對(duì)象的load方法,從字面意思上我們也可以理解,它的用途是原始追蹤yml的加載器。中間一連串的方法調(diào)用可以忽略,直接看最后也是最重要的是一步,調(diào)用OriginTrackingConstructor對(duì)象的getData接口,來(lái)解析yml并封裝成對(duì)象。

在解析yml的過(guò)程中實(shí)際使用了Composer構(gòu)建器來(lái)生成節(jié)點(diǎn),在它的getNode方法中,通過(guò)解析器事件來(lái)創(chuàng)建節(jié)點(diǎn)。通常來(lái)說(shuō),它會(huì)將yml中的一組數(shù)據(jù)封裝成一個(gè)MappingNode節(jié)點(diǎn),它的內(nèi)部實(shí)際上是一個(gè)NodeTuple組成的List,NodeTuple和Map的結(jié)構(gòu)類似,由一對(duì)對(duì)應(yīng)的keyNode和valueNode構(gòu)成,結(jié)構(gòu)如下:

好了,讓我們?cè)倩氐缴厦娴哪菑埛椒ㄕ{(diào)用流程圖,它是根據(jù)文章開(kāi)頭的yml文件中實(shí)際內(nèi)容內(nèi)容繪制的,如果內(nèi)容不同調(diào)用流程會(huì)發(fā)生改變,大家只需要明白這個(gè)原理,下面我們具體分析。

首先,創(chuàng)建一個(gè)MappingNode節(jié)點(diǎn),并將switch封裝成keyNode,然后再創(chuàng)建一個(gè)MappingNode,作為外層MappingNode的valueNode,同時(shí)存儲(chǔ)它下面的4組屬性,這也是為什么上面會(huì)出現(xiàn)4次循環(huán)的原因。如果有點(diǎn)困惑也沒(méi)關(guān)系,看一下下面的這張圖,就能一目了然了解它的結(jié)構(gòu)。

在上圖中,又引入了一種新的ScalarNode節(jié)點(diǎn),它的用途也比較簡(jiǎn)單,簡(jiǎn)單String類型的字符串用它來(lái)封裝成節(jié)點(diǎn)就可以了。到這里,yml中的數(shù)據(jù)被解析完成并完成了初步的封裝,可能眼尖的小伙伴要問(wèn)了,上面這張圖中為什么在ScalarNode中,除了value還有一個(gè)tag屬性,這個(gè)屬性是干什么的呢?

在介紹它的作用前,先說(shuō)一下它是怎么被確定的。這一塊的邏輯比較復(fù)雜,大家可以翻一下ScannerImpl類fetchMoreTokens方法的源碼,這個(gè)方法會(huì)根據(jù)yml中每一個(gè)key或value是以什么開(kāi)頭,來(lái)決定以什么方式進(jìn)行解析,其中就包括了{(lán)、[、'、%、?等特殊符號(hào)的情況。以解析不帶任何特殊字符的字符串為例,簡(jiǎn)要的流程如下,省略了一些不重要部分:

在這張圖的中間步驟中,創(chuàng)建了兩個(gè)比較重要的對(duì)象ScalarToken和ScalarEvent,其中都有一個(gè)為true的plain屬性,可以理解為這個(gè)屬性是否需要解釋,是后面獲取Resolver的關(guān)鍵屬性之一。

上圖中的yamlImplicitResolvers其實(shí)是一個(gè)提前緩存好的HashMap,已經(jīng)提前存儲(chǔ)好了一些Char類型字符與ResolverTuple的對(duì)應(yīng)關(guān)系:

當(dāng)解析到屬性on時(shí),取出首字母o對(duì)應(yīng)的ResolverTuple,其中的tag就是tag:yaml.org.2002:bool。當(dāng)然了,這里也不是簡(jiǎn)單的取出就完事了,后續(xù)還會(huì)對(duì)屬性進(jìn)行正則表達(dá)式的匹配,看與regexp中的值是否能對(duì)的上,檢查無(wú)誤時(shí)才會(huì)返回這個(gè)tag。

到這里,我們就解釋清楚了ScalarNode中tag屬性究竟是怎么獲取到的了,之后方法調(diào)用層層返回,返回到OriginTrackingConstructor父類BaseConstructor的getData方法中。接下來(lái),繼續(xù)執(zhí)行constructDocument方法,完成對(duì)yml文檔的解析。

調(diào)用構(gòu)造器

在constructDocument中,有兩步比較重要,第一步是推斷當(dāng)前節(jié)點(diǎn)應(yīng)該使用哪種類型的構(gòu)造器,第二步是使用獲得的構(gòu)造器來(lái)重新對(duì)Node節(jié)點(diǎn)中的value進(jìn)行賦值,簡(jiǎn)易流程如下,省去了循環(huán)遍歷的部分:

推斷構(gòu)造器種類的過(guò)程也很簡(jiǎn)單,在父類BaseConstructor中,緩存了一個(gè)HashMap,存放了節(jié)點(diǎn)的tag類型到對(duì)應(yīng)構(gòu)造器的映射關(guān)系。在getConstructor方法中,就使用之前節(jié)點(diǎn)中存入的tag屬性來(lái)獲得具體要使用的構(gòu)造器:

當(dāng)tag為bool類型時(shí),會(huì)找到SafeConstruct中的內(nèi)部類 ConstructYamlBool作為構(gòu)造器,并調(diào)用它的construct方法實(shí)例化一個(gè)對(duì)象,來(lái)作為ScalarNode節(jié)點(diǎn)的value的值:

在construct方法中,取到的val就是之前的on,至于下面的這個(gè)BOOL_VALUES,也是提前初始化好的一個(gè)HashMap,里面提前存放了一些對(duì)應(yīng)的映射關(guān)系,key是下面列出的這些關(guān)鍵字,value則是Boolean類型的true或false:

到這里,yml中的屬性解析流程就基本完成了,我們也明白了為什么yml中的on會(huì)被轉(zhuǎn)化為true的原理了。

思考

那么,下一個(gè)問(wèn)題來(lái)了,既然yml文件解析中會(huì)做這樣的特殊處理,那么如果換成properties配置文件怎么樣呢? 

  1. sw.turnOn=on  
  2. sw.turnOff=off 

執(zhí)行一下程序,看一下結(jié)果:

可以看到,使用properties配置文件能夠正常讀取結(jié)果,看來(lái)是在解析的過(guò)程中沒(méi)有做特殊處理,至于解析的過(guò)程,有興趣的小伙伴可以自己去閱讀一下源碼。 

 

責(zé)任編輯:龐桂玉 來(lái)源: Java知音
相關(guān)推薦

2022-01-13 17:24:04

SpringBootYml監(jiān)聽(tīng)器

2025-05-28 08:35:00

Nacos服務(wù)訂閱流程開(kāi)發(fā)

2025-06-03 08:25:00

Nacos開(kāi)發(fā)服務(wù)

2025-06-04 04:00:00

Spring掃碼登錄免密認(rèn)證

2025-06-09 07:11:56

2022-07-27 09:53:06

神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)

2018-11-28 15:15:52

大數(shù)據(jù)AI安防

2015-06-24 10:51:10

iOS學(xué)習(xí)流程

2022-03-18 13:58:00

RocketMQ消息隊(duì)列

2021-06-16 17:45:24

javaMESA模型

2021-09-29 11:30:01

大數(shù)據(jù)技術(shù)架構(gòu)

2021-04-19 07:57:23

Spring 源碼GetBean

2024-03-18 07:48:00

大語(yǔ)言模型NVIDIA生成式 AI

2025-07-02 08:10:01

StarRocks物化視圖MV

2021-04-23 10:55:52

人工智能深度學(xué)習(xí)

2025-02-10 14:13:54

SQL語(yǔ)句query

2025-06-26 02:11:00

2024-08-07 08:19:13

2022-09-26 11:32:14

用戶分層服務(wù)業(yè)務(wù)

2021-03-18 12:16:44

用戶分層業(yè)務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)