喜歡寫代碼,討厭配環境
我相信這十個字的小標題代表了大多數碼農的心聲。
十年前讀大學時,學校開設了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相關的源,就是ServletConfig和ServletContext。
可以從它們兩個里面取出初始化參數,而且它們的優先級僅次於命令行參數。
備注:還有一種基於Reactive(響應式)的web環境。暫時先不討論了。
每一個源里面其實都是key-value,內容較多,不再展示。可以自己運行下試試。
本文示例代碼:
https://github.com/coding-new-talking/playing-spring.git
>>> 玩轉SpringBoot系列文章 <<<
【玩轉SpringBoot】用好條件相關注解,開啟自動配置之門
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有沒有本質的不同?
品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”
品Spring:SpringBoot發起bean定義注冊的“二次攻堅戰”
品Spring:注解之王@Configuration和它的一眾“小弟們”
品Spring:對@PostConstruct和@PreDestroy注解的處理方法
品Spring:對@Autowired和@Value注解的處理方法
品Spring:真沒想到,三十步才能完成一個bean實例的創建
品Spring:關於@Scheduled定時任務的思考與探索,結果尷尬了
>>> 熱門文章集錦 <<<
爸爸又給Spring MVC生了個弟弟叫Spring WebFlux
【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)
【面試】如果你這樣回答“什么是線程安全”,面試官都會對你刮目相看(建議珍藏)
【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)
【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號的二維碼,歡迎關注!