javascript
Spring Environment全解析
文章目錄
- 一、前言
- 二、Spring Environment簡介
- 2.1 env 的 profiles 不同環境配置分組
- 2.1.1 env 的 profiles 基本使用
- 2.1.2 配置環境三地方:啟動類、application.properties配置文件、Run/Debug Configuration
- 2.1.3 setActiveProfiles()參數是數組,可以設置多個
- 2.2 env 的 properties:存放屬性的環境或文件信息 @Value("{xxx}")
- 2.2.1 用@Value取出普通配置
- 2.2.2 用@Value取出系統配置,如java.version
- 2.2.3 用Environment實例bean取出系統配置,如java.version
- 2.2.4 為什么可以用Environment實例bean取出java.version這種系統配置
- 三、Spring Environment源碼解析
- 3.1 從run()方法開始
- 3.2 第一子方法:新建或獲取環境getOrCreateEnvironment()方法
- 3.2.1 AbstractEnvironment類
- 3.2.2 StandardEnvironment類
- 3.2.3 StandardServletEnvironment類
- 3.2.4 MutablePropertySources類
- 3.3 第二子方法:準備環境configureEnvironment()
- 3.3.1 SpringApplication類中的configureEnvironment()
- 3.3.2 解析properties屬性:SpringApplication類的configurePropertySources()
- 3.3.3 SpringApplication類的configureProfiles()
- 3.4 第三子方法:開始加載springboot的配置listeners.environmentPrepared(environment)
- 3.4.1 listeners.environmentPrepared(environment)
- 3.4.2 onApplicationEnvironmentPreparedEvent()
- RandomValuePropertySource類
- 四、尾聲
一、前言
二、Spring Environment簡介
2.1 env 的 profiles 不同環境配置分組
2.1.1 env 的 profiles 基本使用
首先,我們知道,整個spring應用運行的環境信息:profiles + properties
先看profiles,在代碼中的配置為:
spring.profiles.active=prd/test/dev...profiles作用:對bean邏輯分組
先定義一個ProfileService類,包含一個私有屬性profile,如下:
新建一個ProfileConfiguration配置類,將新建的ProfileService類的實例bean定義在這里,等到springboot項目啟動的時候,將可以將ProfileService實例bean裝載的ioc容器中去了,整個如下:
在ProfileConfiguraiton配置類的各個bean上,加上@Profile注解,模擬設置兩種不同環境下的bean,聲明不同環境的bean
金手指:使用@Profile對不同的bean邏輯分布 xml 和注解都可以
現在我們不使用SpringbootApplication類啟動了,直接寫一個main方法啟動即可,如下,在啟動之前,在Environment里面設置profile,setActiveProfile()蠶食是數組,可以設置躲著,這里測試之用,先設置“prd”,最后context.getBean可以取出ioc容器中的bean,打印到控制臺就好。
一般來說,getBean和@Autowired兩種方式都可以取出ioc容器中的bean.
運行springboot工程,如果打印出來的ioc容器中的bean,prd環境中的bean,就可以了。
至此,對于env 的 profiles設置成功。
2.1.2 配置環境三地方:啟動類、application.properties配置文件、Run/Debug Configuration
我們能夠配置環境的地方不止一個,不僅像上面在啟動類中可以配置,還是在application.properties配置文件中配置,還可以在 Run/Debug Configuration 中配置
2.1.3 setActiveProfiles()參數是數組,可以設置多個
setActiveProfiles()參數是數組,可以設置多個,如下:
至此,對于 env 的 profiles 的講解完成。
2.2 env 的 properties:存放屬性的環境或文件信息 @Value(“{xxx}”)
2.2.1 用@Value取出普通配置
新建一個UserController類,打印出application.properties配置文件中的env屬性即可,如下:
application.properties配置文件中的env屬性為“hello world”,配置如下:
運行成功,取出來了,如下:
所以,我們看到,properties存放屬性的環境或文件信息。
2.2.2 用@Value取出系統配置,如java.version
實際上,@Value注解還可以訪問到系統環境變量的信息,如java.version,取出當前jdk版本,試一試:
看,打印出來了。
2.2.3 用Environment實例bean取出系統配置,如java.version
我們用@Autowired取出spring ioc容器中的Environment實例bean,然后直接用Environment實例bean(不用@Value)來取,如下:
取出spring ioc容器中的實例bean有兩種方式,getBean方法 或 @Autowired注解。
看,也打印出來了。
2.2.4 為什么可以用Environment實例bean取出java.version這種系統配置
那問題來了,為什么可以用Environment取出java.version這種系統配置呢?一起來看看Spring中的Environment接口,如下:
理由很簡單,因為Environment實例bean已經在ioc容器中了,所以要取出系統配置java.version很簡單,因為這個系統配置java.version就是寫在Environment的實例bean里面的。
其他的,如BeanFactory、ApplicationContext實例bean也是在ioc容器里的,還實現了Environment接口,如下:
其實,作為Spring容器的內置接口,Environment中的配置有很多來源:系統的環境變量(如上面提到的java.version)、系統變量 System.propreties等。
大體上來說,Environment中的配置來源大體包括兩大類:系統屬性源 + springboot屬性源,如下:
這個圖很重要,本文接下來就講這個圖,講Environment是如何處理 “系統屬性源” 和 “springboot屬性源” 的。
金手指:springboot啟動類自動掃描所在包及其子包下spring+springmvc注解,但是不會掃描ibatis的@Mapper注解
三、Spring Environment源碼解析
先找到Environment接口
3.1 從run()方法開始
找打springboot工程的run方法,如下:
不斷ctrl+左鍵,進入到真正有意義的run方法中,如下:
在這個真正有意義的run方法中,使用prepareEnvironment方法得到一個env實例,然后將這個實例設置到context中去,如下:
金手指:源碼中,spring或springboot初始化加載時,env 在 context 之前,合乎常理。這里看的springboot的源碼,其實spring的源碼也一樣。
選擇prepareEnvironment方法
進入prepareEnvironment方法,先使用getOrCreateEnvironment方法新建或獲得一個環境變量,然后將這個env實例放到configureEnvironment方法中去完成相關配置,最后通過listeners.environmentPrepared方法,將env實例交給監聽事件,如下:
接下來看一下SpringApplication類prepareEnvironment()方法的三個子方法。
3.2 第一子方法:新建或獲取環境getOrCreateEnvironment()方法
可以看到,如果類變量env不為null,就直接返回,如果為null,就根據類變量webApplicationType的實例類型,new一個Env實現類實例返回。
所以說,getOrCreateEnvironment()方法中涉及三個類:StandardEnvironment StandardServletEnvironment StandardReactiveWebEnvironment,這三個類都繼承AbstractEnvironment,AbstractEnvironment又繼承于Environment接口,關系如下:
先看AbstractEnvironment類
3.2.1 AbstractEnvironment類
在AbstractEnvironment的構造方法中,調用一個自定義屬性源的方法,如下:
這個自定義屬性源的方法,在AbstractEnvironment中是空實現,只能看它的子類了。
值得注意的是,對于AbstractEnvironment構造函數,
public AbstractEnvironment() {customizePropertySources(this.propertySources); }這是一個很優美的設計,可以將customizePropertySources的實現交給子類的處理,是模板模式。
AbstractEnvironment類就到這里的,我們看其子類StandardEnvironment類。
3.2.2 StandardEnvironment類
既然AbstractEnvironment類的customizePropertySources()是空實現,看看其子類StandardEnvironment類。
StandardEnvironment類只有一個customizePropertySources方法實現,也看完了,且看StandardServletEnvironment類。
3.2.3 StandardServletEnvironment類
直接轉到customizePropertySources方法實現,如下:
關于customizePropertySources()方法,customizePropertySources()接收可變參數源作為輸入。
該方法在AbstractEnvironment類中為空實現,在StandardEnvironment類中為將系統配置和系統環境變量放到env中,在StandardServletEnvironment類中為servlet配置屬性、servlet上下文屬性、JNDI屬性,并調用StandardEnvironment的customizePropertySources()方法,所以同時有了servlet配置屬性、servlet上下文屬性、JNDI屬性、系統配置和系統環境變量五種屬性。
源碼設計的優美之處1:StandardServletEnvironment類中的customizePropertySources()方法調用了StandardEnvironment的customizePropertySources()方法,因為是它里面將系統配置和系統環境變量放到env中;StandardEnvironment的customizePropertySources()方法沒有調用AbstractEnvironment的customizePropertySources()方法,因為是它里面將里面方法體為空,不需要調用;這體現了源碼的優美。
源碼設計優美之處2:
public AbstractEnvironment() {customizePropertySources(this.propertySources); }AbstractEnvironment類的構造方法中調用customizePropertySources(),以后customizePropertySources()一層層被重寫,反正調用最后子類的,具體環境生產具體對象,這體現了源碼設計優美。
3.2.4 MutablePropertySources類
customizePropertySources()接收可變參數源作為輸入,實際上就是一個MutablePropertySources類對象,讓我們來看一下這個MutablePropertySources類,其定義如下:
讓我們看看StandardServletEnvironment添加的三個屬性是什么
在我們使用spring+springmvc的時候,需要配置一個web.xml,web.xml里面需要配置兩個標簽< context-params>< /context-params>和< init-params>< /init-params>,當時我們只是這樣這樣使用,實際上,web.xml中這兩個標簽作為屬性配置到env里面去了。
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = “servletContextInitParams”; 就是 < context-params>< /context-params>
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = “servletConfigInitParams”; 就是 < init-params>< /init-params>
我們知道,StandardEnvironment:系統變量+系統環境變量
/** System environment property source name: {@value}. */ public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";/** JVM system properties property source name: {@value}. */ public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";上面,前者表示系統環境變量,所以我們剛才使用@Value(“${java.version}”)訪問到了系統環境變量,后者表示系統屬性。
我們現在完善圖,一圖小結源碼結構:
最后一個問題,配置順序,優先級如何確定?
回答:不管是StandardEnvironment類和還是StandardServletEnvironment類,都是使用addLast添加屬性的,所有屬性的順序就是源代碼從上到下。
好了,ConfigurableEnvironment environment = getOrCreateEnvironment(); 這行代碼完了,回到SpringApplication類,看configureEnvironment(environment, applicationArguments.getSourceArgs());這行代碼。
3.3 第二子方法:準備環境configureEnvironment()
3.3.1 SpringApplication類中的configureEnvironment()
configureEnvironment()是在新建/獲取env實例后執行的,如下:
在configureEnvironment()方法中,第一個實參就是ConfigurableEnvironment類對象,就是env對象,剛剛看過了,第二個參數是一個DefaultApplicationArguments類對象.getSourceArgs(),它是在源碼中new出來的,如下(簡答了解就好,只要知道命令行參數也是可以被識別的就好):
進入到重點,看到configureEnvironment方法,如下:
類變量addConversionService 默認為true,
private boolean addConversionService = true;它表示統一類型轉換,當其為true,執行if代碼塊,得到一個conversionService,并設置到Spring框架的env中去。
接著往下看,可以看到,configureEnvironment方法先后執行了兩個方法,就是 configurePropertySources(environment, args); 和 configureProfiles(environment, args); ,就是整個spring應用運行的環境信息:profiles + properties,就是在這兩個方法里面解析出來的。
3.3.2 解析properties屬性:SpringApplication類的configurePropertySources()
看到configurePropertySources()方法,這個方法解析開發者配置的所有properties屬性。
先獲得env實例中的屬性源,如果默認屬性不為空,將其添加到sources中,addLast就是在list末尾添加,如果啟動的時候,命令行中也傳入了參數,也添加進來,沒傳入也沒關系。
小結,繼續更新env圖
env中添加命令行參數很好懂,但是默認參數defaultproperties是什么?從哪里來的?先看這個defaultproperties的定義,它在SpringApplication類中,默認是一個map結構,定義如下:
值得注意的是,這個defaultproperties僅僅在springboot才有的,spring是沒有的。
關于這個defaultproperties,可以自己設置 setDefautProperties,嘗試一下,如下面springboot啟動類中。
運行啟動起來,看,在源碼中斷點,真的走到了這個地方,取出defaultProperties變量,看到自己手動添加的屬性。
好了,configurePropertySources()方法完成了,下面看configureProfiles()方法。
3.3.3 SpringApplication類的configureProfiles()
先打開configureProfiles()的源碼,看到就是根據類變量additionalProfiles新建一個LinkedHashSet,然后將env變量中的profiles屬性都放到這個新建的set中,最后將這個set變為字符串又放到env中去。
好了,我們進入到setActiveProfiles()方法,如下:
setActiveProfiles()設置env中的profile
關于configureProfiles方法,看起來好像沒什么意義?將env中的profiles放到set中,然后又將set放到env中。
但是,請注意,這里這個AbstractEnvironment類中的setActiveProfiles()方法接收String類型可變數組,同時將字符串類型的profile放到一個Set類型的集合activeProfiles中,因為set集合,可以配置多個,但是不能字符串重名。
實際上,這個方法我們一開始就用到,在env中設置profile,接受一個數組,包含兩個字符串 “prd” 和 “env” ,保證既是數組又不重名。
其實,在application.properties文件中設置和在Run/Debug Configurations中設置都是一個道理,底層都是調用這個方法setActiveProfiles()。
3.4 第三子方法:開始加載springboot的配置listeners.environmentPrepared(environment)
3.4.1 listeners.environmentPrepared(environment)
這里就是一個監聽邏輯了,復習一下,在Spring中,通用的監聽邏輯是:自定義一個事件類,事件發布者發布一個自定義事件,時間接受者listener接收自定義事件及其子類事件。
進入listeners.environmentPrepared(environment);
再進入environmentPrepared方法,如下:
看一下廣播事件的具體邏輯,如下:
再進去invokeListener方法,如下:
這個listener.onApplicationContext(event)就是表示監聽者監聽到事件觸發后要完成的相應邏輯。
好了,我們重溫一下這個調用關系,如下:
好了,我們來看看這個listener監聽到事件發生后的具體操作,進入onApplicationEvent方法,但是這種方法有很多個,如下:
任意找一個,這里找ConfigFileApplicationListener類,可以看到對于通過event類型,執行了不同操作,如下:
對于onApplicationEvent的第二個方法onApplicationPreparedEvent,如下:
EnvironmentPostProcessor有很多實現類,如下:
找到幾個EnvironmentPostProcessor的實現類,這里選擇SpringApplicationJsonEnvironmentPostProcessor類,這個類中,實現了兩個接口,EnvironmentPostProcessor接口是擴展,Ordered接口是排序,如下:
3.4.2 onApplicationEnvironmentPreparedEvent()
關于onApplicationEvent的第一個方法onApplicationEnvironmentPreparedEvent()方法,如下:
再次進入到postProcessEnvironment方法,如下:
好了,整理一下調用關系,如下:
繼續進入到addPropertySources方法,如下:
我們注意到,在ConfigFileApplicationListener類中,里面有三個重要的類變量,DEFAULT_PROPERTIES默認屬性,DEFAULT_SEARCH_LOCATIONS默認掃描位置(開發者的文件應該存放的路徑位置),DEFAULT_NAMES默認名稱(開發者的文件的默認名稱),如下:
在addPropertySources方法中,三步走,先添加,然后新建一個Loader,最后執行load方法,如下:
第一步,先添加,進入RandomValuePropertySource.addToEnvironment方法,如下:
RandomValuePropertySource類
我們先來認識一下這個RandomValuePropertySource類,類上的注釋就告訴我們怎么配置,如下:
類中有兩個常量,默認隨機屬性名稱為random,前綴為random.,如下:
getProperty方法:當實參name前綴不為random.,直接返回為null;當實參name的前綴為random.,調用getRandomValue方法返回一個隨機值。
getRandomValue方法:根據實參name取前綴長度的字符串匹配,返回具體的隨機值,
好了,我們按照RandomValuePropertySource類的配置,自動動手試一試,配置一個name為randomLong,值為random.long,底層是由RandomValuePropertySource類的getRandomValue方法生成的,配置如下:
主代碼中通過@Value或者Environment的bean實例取出屬性值,這里使用Environment的bean實例,如下:
運行,真的取出來了
刷新一次又變了,每一次都會生成一個隨機long,哈哈
好了,玩夠了,回到ConfigFileApplicationListener類的addPropertySources方法:添加,新建Loader,執行load方法。
進入到addToEnvironment()方法,這里的addAfter就是在后面添加隨機屬性。
env如圖:
回到ConfigFileApplicationListener類,查看new Loader(environment, resourceLoader).load();
Loader是資源文件加載器
這個Loader類的構造方法就是設置四個類屬性,如下:
看到第四句,如下:
先看到這個SpringFactoriesLoader.loadFactories方法,這個方法就是加載spring.factories配置文件中的數據,
再看PropertySourceLoader接口本身,這個接口僅包含兩個方法,獲取文件后綴名方法和加載方法。
看到load方法
進入FilteredPropertySource.apply()方法,如下:
回到load方法,看到initializeProfiles方法
進入到initializeProfiles方法,該方法初始化profiles屬性,邏輯如下:
進入到getOtherActiveProfiles方法,如下:
經歷了Loader構造函數,有了Loader對象,看一下load方法,如下:
在進入load方法,如下:
debug執行,如下:
進入到loadForFileExtension方法,如下:
四、尾聲
Spring Environment全解析,完成了。
天天打碼,天天進步!!
總結
以上是生活随笔為你收集整理的Spring Environment全解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Centos下安装FastDFS
- 下一篇: gradle idea java ssm