【玩轉SpringBoot】看似復雜的Environment其實很簡單



喜歡寫代碼,討厭配環境



我相信這十個字的小標題代表了大多數碼農的心聲。

十年前讀大學時,學校開設了C語言還有C++。但是學習這兩種語言,對於新手來說非常沒有成就感。

於是我就在校門口買個光盤,裝個VS(宇宙第一IDE),還有離線中文版MSDN(最牛的幫助文檔),萬事已俱備。

學習C#語法,看類的API,然后從WinForm(窗口)開始,用鼠標拖拽控件,設置控件屬性,觀察自動生成的代碼,開啟人生的編程之路。

大四的時候接觸到Java,首先就是配置環境變量,那時覺得是一個巨復雜的東西,每次都要網上搜好一會兒才能配好。

我學習微軟的東西從來就不要配什么環境變量,心里很討厭這個Java的環境變量,這就導致十年后的今天,我依然要去網上搜如何配置,慚愧慚愧,哈哈。

后來發現,基本上軟件都要配置一些環境變量,只不過有的是在安裝時已經自動配好了而已,但是對於免安裝版(直接解壓)的則需要自己配。


我們也來嘗試下抽象


熟悉Java的都知道,Java里面有兩個內置的配置集合,就是System.getenv()System.getProperties()

它們分別是系統環境和系統屬性,如下圖01:


一個是Map類型,一個是Properties類型,說明它們都是一些key-value形式的值。

而且Properties類型是Java里的標准配置方式,它就對應於*.properties文件。

至此,我們已經發現兩個問題:

1)配置項都是以key-value形式存在的。

2)配置項的來源是多樣化的,如現在的系統環境、系統屬性、配置文件等,后期還可能會有其它。

對於配置項的多來源問題,有以下兩種方式解決:

1)可以把所有來源都暴露給用戶,這樣使用起來更加精細,但是也會帶來困擾,可能用戶也會迷糊到底該去哪個來源取值。

2)在所有來源前面加一個“門面”,只把它暴露出去,用戶看到的只是“單一來源”,就從這里取值,其它的啥也不用知道。

Spring選擇的是第二種方案,拿到key后,只需依次去每個來源中查找,這時只需規定下多個來源之間的優先級順序即可。

整體可以用一個圖形表示,如下圖02:


這整體也是一種封裝變化的思想,底層的多來源問題被封裝起來,對用戶不可見。

最終用戶傳過來一個key,我給返回一個和它對應的value就行了。


來來來,認識兩個朋友


配置項在Java中通常叫做屬性,即Property。每一個來源其實就是一個源泉,即Source。

所以在Spring中就用PropertySource類來表示一個來源,如下圖03:


注意兩個字段,name和source,name就是為每個源起個名字。source表示真正的資源,是能從中取出value的東西。

然后需要一個門面把多個來源封裝起來,如下圖04:


可以看到它里面有一個來源的列表。這就是封裝。而且還是有順序的。

根據key取值就依次遍歷所有源即可,如下圖05:


如果所有源中都沒找到,返回null就行了。

這樣配置項(或配置屬性)的問題就已經解決了,很簡單吧。


除了配置屬性外,還有Profile


配置屬性是一個很泛化的概念,說白了它就表示以非寫代碼的方式從外界向程序中傳遞特定的值。

它的好處就是修改起來很容易,只需修改下配置文件或命令行參數,然后最多重啟一下就可以了。

不用修改代碼,自然不用重新編譯,當然也不用重新打包發布。

泛化其實就表示囊括所有的意思,但是總會有一些特殊情形,值得單獨拿出來特別對待。

如每個軟件都會至少經歷開發、測試、上線這三個階段,同樣也會有三套環境,即開發環境、測試環境、生產環境。

這里的“環境”其實就是一個特殊情況,我們把它單獨拿出來,就叫做Profile。

對不同的環境設置不同的Profile,程序中可以讀取到Profile,這樣程序就可以適應不同的Profile,展示不同的特性。

最終就可以一套代碼打天下。就像華為的一套操作系統適應所有的終端設備。就像Java的一份字節碼運行在不同的操作系統上。

在不指定Profile時,通常應該有一個默認的Profile。就像汽車默認是運行在城市道路上一樣。

在Spring中,默認的Profile就叫做default。如下圖06:


這個default可能沒有什么意義,所以Spring提供了修改它的機會,如下圖07:


可以使用圖中的參數名稱去指定默認的Profile,以符合自己的使用習慣。

比如對於汽車這種情況,可以這樣:

spring.profiles.default=city

我們也可以為不同的環境激活不同的Profile,Spring也提供了方法,如下圖08:


比如汽車上了高速,我們想狂野一下,可以激活運動模式:

spring.profiles.active=sports


最后要說的就是,這個Profile可以指定多個,用逗號分隔即可。

因為Spring是用集合存儲的,所以支持多個,如下圖09:


程序在判斷哪些Profile被激活時,可以使用邏輯表達式,這樣就更加靈活了。

支持與、或、非、括號,如下圖10:


比如我們讓程序運行在單節點debug模式,可以這樣設置:

spring.profiles.active=standalone,debug


那么下面這些判斷將都返回true:

standalone -> true

debug -> true

standalone | debug -> true

standalone & debug -> true

!other -> true

!unknown -> true


下面這些將都返回false:

!standalone -> false

!debug -> false

standalone & other -> false

debug & unknown -> false

(standalone & other) | (debug & unknown) -> false

(standalone | debug) & other & unknown -> false


:當同時出現與(&)、或(|)時,一定要使用括號。


那什么是Environment呢?


很簡單,就是這個公式:

Environment = Properties + Profiles


表示Properties的接口,主要就是處理一些key-value,如下圖11:


Environment繼承了這個接口,又加入處理Profile的內容,如下圖12:


由於要支持key-value數據類型的轉換和${..}表達式的解析,所以需要能夠配置,如下圖13:


由於需要能夠以編程的方式激活Profile或設置默認Profile,所以也需要能夠配置,如下圖14:


所以,這四個接口就是Spring環境的全部了。


在SpringBoot中Environment的真面目


下面是非web環境:

StandardEnvironment {activeProfiles=[], defaultProfiles=[default], 
propertySources=[
ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, 
SimpleCommandLinePropertySource {name='commandLineArgs'}, 
PropertiesPropertySource {name='systemProperties'}, 
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, 
RandomValuePropertySource {name='random'}, 
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}, 
ResourcePropertySource {name='class path resource [mode.properties]'}, 
ResourcePropertySource {name='class path resource [greeting.properties]'}]}


可以看到配置屬性有多個來源,包括命令行參數,系統屬性,系統環境,隨機數,yml配置文件,properties配置文件等。

--開頭的參數會出現在命令行參數這個源里,如下圖15:


-D開頭的參數會出現在系統屬性這個源里,如下圖16:


這些源在上面的順序就是它們的優先級,可見命令行的最高,properties文件的最低。

注意源中的第一個,即名稱為configurationProperties的,主要是為了適應SpringBoot的屬性名的“松散”綁定而專門用來處理屬性名稱的。

它並不真正提供屬性值,它的值來源於除它之外的其它源。

如果不明白什么是屬性名的松散綁定的,看這個示例:

user-name, user_name, userName


這三個屬性名稱都可以綁定到一個類的userName屬性上。

下面是基於Servlet的web環境:

StandardServletEnvironment {activeProfiles=[], defaultProfiles=[default], 
propertySources=[
ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, 
SimpleCommandLinePropertySource {name='commandLineArgs'}, 
StubPropertySource {name='servletConfigInitParams'}, 
ServletContextPropertySource {name='servletContextInitParams'}, 
PropertiesPropertySource {name='systemProperties'}, 
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, 
RandomValuePropertySource {name='random'}, 
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}, 
ResourcePropertySource {name='class path resource [mode.properties]'}, 
ResourcePropertySource {name='class path resource [greeting.properties]'}]}


和上面的唯一區別就是多了兩個和web相關的源,就是ServletConfigServletContext

可以從它們兩個里面取出初始化參數,而且它們的優先級僅次於命令行參數。

備注:還有一種基於Reactive(響應式)的web環境。暫時先不討論了。

每一個源里面其實都是key-value,內容較多,不再展示。可以自己運行下試試。

本文示例代碼

https://github.com/coding-new-talking/playing-spring.git

 

>>> 玩轉SpringBoot系列文章 <<<

 

【玩轉SpringBoot】配置文件yml的正確打開姿勢

【玩轉SpringBoot】用好條件相關注解,開啟自動配置之門

【玩轉SpringBoot】給自動配置來個整體大揭秘

 

>>> 品Spring系列文章 <<<

 

品Spring:帝國的基石

品Spring:bean定義上梁山

品Spring:實現bean定義時采用的“先進生產力”

品Spring:注解終於“成功上位”

品Spring:能工巧匠們對注解的“加持”

品Spring:SpringBoot和Spring到底有沒有本質的不同?

品Spring:負責bean定義注冊的兩個“排頭兵”

品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”

品Spring:SpringBoot發起bean定義注冊的“二次攻堅戰”

品Spring:注解之王@Configuration和它的一眾“小弟們”

品Spring:bean工廠后處理器的調用規則

品Spring:詳細解說bean后處理器

品Spring:對@PostConstruct和@PreDestroy注解的處理方法

品Spring:對@Resource注解的處理方法

品Spring:對@Autowired和@Value注解的處理方法

品Spring:真沒想到,三十步才能完成一個bean實例的創建

品Spring:關於@Scheduled定時任務的思考與探索,結果尷尬了

 

>>> 熱門文章集錦 <<<

 

畢業10年,我有話說

【面試】我是如何面試別人List相關知識的,深度有點長文

我是如何在畢業不久只用1年就升為開發組長的

爸爸又給Spring MVC生了個弟弟叫Spring WebFlux

【面試】我是如何在面試別人Spring事務時“套路”對方的

【面試】Spring事務面試考點吐血整理(建議珍藏)

【面試】我是如何在面試別人Redis相關知識時“軟懟”他的

【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)

【面試】如果你這樣回答“什么是線程安全”,面試官都會對你刮目相看(建議珍藏)

【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)

【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)

【面試】如果把線程當作一個人來對待,所有問題都瞬間明白了

Java多線程通關———基礎知識挑戰

品Spring:帝國的基石

 

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號的二維碼,歡迎關注!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM