Spring Boot支持的配置方式有哪些(深度解析)
有本書上總結了一下,大概有9種配置方式,當然還有其他方式,這裏不再列舉。並且這些配置方式是有優先級的,如果不同的配置方式都配置了同一個配置項,那麼優先級高的勝出。下面我們來具體看。
一,Spring Boot支持的配置方式
Spring Boot配置的核心類是PropertySource<T>,這個類是抽象類,定義了獲取配置信息的基本操作。
下面我們結合源碼和測試驗證,來具體分析。
1,命令行參數。
什麼是命令行參數呢?就是我們啓動Spring Boot時,SpringApplication#run方法的入參中的第二個參數args。舉個例子:
SpringApplication.run(XxxApplication.class, "--spring.redis.timeout=60000");
這種方式就是命令行參數,但是我們一般不會這樣來配置,我們通常是這樣寫的:
SpringApplication.run(XxxApplication.class, args);
Spring Boot給我們提供了這種方式來配置,實際生產上我們不會使用,平時測試的時候可以使用,可以協助我們定位問題。
對應的解析類:CommandLinePropertySource<T>
2,通過System#getProperties方法獲取的java系統參數。
這部分系統參數,本質上是通過System#initProperties這個native方法來獲取的,這部分的優先級低於命令行參數配置方式。
舉個例子:假如我想覆蓋os.name這個配置項,那麼我可以使用命令行參數配置方式,如下:
SpringApplication.run(XxxApplication.class, "--os.name=IOS");
從這裏,我們也可以看出一個問題:命令行參數配置方式有很大的危險性,加入我通過命令行參數配置方式更改了系統參數,可能對系統來說,是災難性的。還好,Spring Boot給我們提供了禁用命令行參數配置的功能。
代碼如下:
// 創建springApplication實例
SpringApplication springApplication = new SpringApplication(XxxApplication.class);
// 禁用命令行參數
springApplication.setAddCommandLineProperties(false);
springApplication.run("--os.name=IOS");
3,操作系統環境變量。使用Docker啓動時,經常會設置系統變量。
這部分系統環境變量,我理解就是通過SyetemEnvironmentProropertySource導入的。正常情況下,也是通過native方法來獲取的,個人理解,只有通過命令行參數配置方式可以覆蓋,沒有其他修改入口。
4,從java:comp/env得到的JNDI屬性。
對應的解析類:JndiPropertySource
這種方式比較老了,本質上是通過FactoryBean的方式注入bean到IOC容器。
舉個例子:
Spring JNDI數據源配置信息:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jcptDataSourceJNDI</value>
</property>
</bean>
關於JNDI的配置(tomcat):
修改tomcat目錄conf/context.xml文件:
<Resource name="jcptDataSourceJNDI" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000" username="tysp"
password="12345678" driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@192.168.1.1:1521:OA"/>
通過JNDI獲取DataSource:
Context context = new InitialContext();
DataSource ds = (DataSource)context.lookup("java:comp/env/jcptDataSourceJNDI");
目前在Spring Boot中,這種方式很少使用了,我們一般是通過配置spring.datasource.*的方式來配置數據源,直接在properties文件或者yml文件中進行配置即可。
5,通過RandomValuePropertySource生成的random.*屬性。
對應的解析類:RandomValuePropertySource。
RandomValuePropertySource的使用也很少,Spring爲什麼要提供這個類呢?個人理解,是爲了在應用啓動階段,提供生產隨機數的功能,因爲有些場景可能會用到,比如每次啓動使用隨機的參數。一旦進入程序運行階段的話,我們就可以自己創建一個Random的實例了!
舉個例子:
xxx.uuid=${random.uuid}
xxx.intValue=${random.int}
redis.key.expireTime=${random.int[60000,120000]}
要注意,這個值會在項目啓動時生成並固定,每次重新啓動項目都會重新生成。
因爲RandomValuePropertySource的優先級高於我們的application.properties,所以我們可以在application.properties中使用random對象。
6,應用jar之外的屬性文件,如通過spring.config.location參數指定的屬性文件。
Spring boot 的Application.properties 配置文件可以是以下幾個地方:
classpath:/,classpath:/config/,file:./,file:./config/.
這4個路徑是默認的搜索路徑,我們還可以自定義配置文件存放路徑,就是通過spring.config.location來指定。
7,應用jar內部的屬性文件。
這一塊就是我們最熟悉的application.properties/application.yml配置文件了。具體的實現類是ConfigFileApplicationListener$ConfigurationProropertySource。
8,應用配置類(包含@Configuration註解的java類)中通過@PropertySource註解申明的屬性文件。
這種配置方式的優先級比較低,如果和我們系統默認的配置文件(application.properties)存在重複的配置項的話,將會被覆蓋。
9,通過SpringApplication#setDefaultProperties方法申明的默認屬性。
具體的實現類是MapProropertySource。
這部分配置最終會存放在Environment的defaultProperties這個屬性中。
二,Spring Boot配置優先級
接下來我們重點看看不同配置方式的優先級問題。
首先要搞清楚一個問題:如果一個配置項,比如:
spring.redis.timeout
我們既在bootstrap.properties中配置了,又在application.properties中配置了,那麼這2個配置項都會保存到我們的StandardServletEnvironment中。
bootstrap.properties中的配置被保存在了applicationConfig屬性中。
application.properties中的配置被保存在了applicationConfigurationProperties屬性中。
關於配置的優先級,我們這裏舉個例子,假如我們在application.properties配置了:
spring.redis.timeout=50000
然後我們在命令行也進行了如下配置:
SpringApplication.run(XxxApplication.class, "--spring.redis.timeout=60000");
因爲命令行參數的優先級高,我們在代碼中引用配置項spring.redis.timeout的話,取到的值是60000。
這就是Spring Boot各配置方式的優先級問題。
三,Spring Boot 項目中,環境Environment中包含的配置屬性
在Spring Boot中,Environment的具體實現是StandardServletEnvironment,StandardServletEnvironment中包含有一個ProropertySource(MutablePropertySource),這個屬性就是Spring Boot所有配置項的集合,裏面包含了所有的配置項。不管是通過哪種方式進行的配置,最終都會放到這個ProropertySource中。
源碼重點關注SpringApplication#prepareEnvironment方法,這個方法就是Spring Boot配置的核心處理方法。這裏有一部分的配置方式是通過監聽器來實現的,在Spring Boot啓動過程中,會觸發ApplicationEnvironmentPreparedEvent事件,然後通過多播器SimpleApplicationEventMulticaster通知監聽器,觸發監聽器的具體邏輯。
ProropertySource中包含以下屬性,這些屬性都是ProropertySource的具體實現。
bootstrapProperties:這個屬性包含了配置中心配置的某個微服務下的xxx_sale.properties配置文件中的所有配置項。
bootstrapProperties的具體實現是:CompositeProropertySource
servletConfigInitParams:具體實現是StubProropertySource。這個屬性加入到MutablePropertySource是在StandardServletEnvironment#customizePropertySources方法中。
servletContextInitParams:具體實現是StubProropertySource。這個屬性加入到MutablePropertySource是在StandardServletEnvironment#customizePropertySources方法中。
jndiProperties:這個屬性加入到MutablePropertySource是在StandardServletEnvironment#customizePropertySources方法中。
jndiProperties的數據結構是JndiPropertySource。
systemProperties:這個屬性包含了jvm的系統信息,比如操作系統相關信息,線程id等。這個屬性加入到MutablePropertySource是在StandardEnvironment#customizePropertySources方法中。感興趣的朋友可以看看源碼,最終是通過System#getProperties這個native方法獲取到系統參數的。systemProperties的數據結構是MapPropertySource。
systemEnvironment:這個屬性包含了jvm運行環境的信息,比如系統環境變量,ant路徑等。這個屬性加入到MutablePropertySource是在StandardEnvironment#customizePropertySources方法中。感興趣的朋友可以看看源碼,最終是通過System#getenv這個native方法獲取到系統參數的。
systemEnvironment的數據結構是SyetemEnvironmentProropertySource。
bootstrap:bootstrap是通過監聽器來實現的,在Spring Boot啓動過程中,會觸發ApplicationEnvironmentPreparedEvent事件,然後通過多播器SimpleApplicationEventMulticaster通知監聽器,觸發監聽器的具體邏輯。
bootstrap的監聽器實現是BootstrapApplicationListener#onApplicationEvent,實現邏輯還是挺複雜的,感興趣的朋友可以深入研究。
bootstrap的數據結構是MapProropertySource。
commandLineArgs:對應命令行參數配置方式,具體實現就在SpringApplication#run方法中,其中解析命令行參數配置的代碼是:
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
把commandLineArgs添加到MutablePropertySource的代碼是:
SpringApplication#configurePropertySources
commandLineArgs的數據結構是SimpleCommandLineProropertySource。
random:具體實現是RandomValueProropertySource。
applicationConfigurationProperties:這個屬性包含了application.properties配置文件中的所有配置項。
具體實現是ConfigFileApplicationListener$ConfigurationProropertySource。
applicationConfig:屬性包含了bootstrap.properties配置文件中的所有配置項。
具體實現是PropertiesProropertySource。
defaultProperties:這個屬性在SpringApplication#configurePropertySources方法中進行設置,最終是應用程序調用SpringApplication#setDefaultProperties進行設置的,優先級較低。defaultProperties的數據結構是MapProropertySource。
springCloudClientHostInfo:具體實現是MapProropertySource。