項目中經常需要使用到占位符來滿足多環境不同配置信息的需求,比如:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myPropertyPlaceholderBean" class="com.example.demo1.PropertyPlaceholderBean"><property name="myPropertyName" value="${my.property.key}" /></bean></beans>
其中屬性myPropertyName是帶有’ ${}’ 符號,也就是占位符的變量,最終需要替換成具體的值,Spring會最終替換,那么它怎么做到的? 下面就通過打斷點跟源碼方式分析來分析說明。
還是以SpringBoot項目為例,在resources下定義結構如下:
以上結構是為了方便驗證,隨便定義的,大家可能有區別。
其中dev.properties定義兩個key
test.env_var=123
my.property.key=justdoit
spring-bean1.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myPropertyPlaceholderBean" class="com.example.demo1.PropertyPlaceholderBean"><property name="myPropertyName" value="${my.property.key}" /></bean></beans>
spring-application.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="classpath:properties/dev.properties" ignore-unresolvable="true" /><import resource="spring-bean1.xml"/>
</beans>
Spring boot啟動類定義
@SpringBootApplication
@ImportResource({"classpath:spring-application.xml"})
public class ClientServerApplication {public static void main(String[] args) {SpringApplication.run(ClientServerApplication.class, args);}
}
好了,下面開始分析整個過程…
首先從PropertySourcesPlaceholderConfigurer開始說,因為占位符默認就是由它來實現的。 進入其源碼看到它是一個BeanFactoryPostProcessor 大家都知道,spring bean生命周期過程會執行所有BeanFactoryPostProcessor的postProcessBeanFactory方法,所以,肯定會進入到這個方法:
這里看到它嘗試從兩個地方去讀取屬性配置,一個是
以Environment為屬性源的environmentProperties,另外一個就是通過loadProperties(Properties props)加載本地資源文件作為屬性源的localProperties,我這個例子是第二種情況。
可以看到,已經加載到我上面配置的兩個key-value
接著進入到下一步:
this.processProperties(beanFactory, (ConfigurablePropertyResolver)(new PropertySourcesPropertyResolver(this.propertySources)));
看到propertyResolver.setPlaceholderPrefix(this.placeholderPrefix)這些是設置缺省時,占位符的默認配置,即’${}’
其中注意一點,StringValueResolver valueResolver定義的是labmda表達式,后面會使用到。
接著下一步
上面這里是開始遍歷所有的bean,替換其中包含占位符的bean的屬性對象。
接著進入方法:
遍歷到我們自定義的bean,其中beanDefinition.getPropertyValues()是拿它的所有屬性信息,如下圖
遍歷所有的屬性,解析值,并且替換占位符
進入resolveValue方法,直接去到以下位置,因為屬性類型是string嘛,所以直接跳到這里
可以看到我們bean中定義的占位符,接下來就是要替換它。接著看
發現此次是一個labmda表達式,就是上面提到的,所以執行回到上面的位置,
接著跟代碼會進入到
繼續進入
parseStringValue
從propertySources里面去解析配置,疑問來了??這個對象什么時候放進去的,其實就是最開始提到的兩個讀取配置的地方,
一個是
以Environment為屬性源的environmentProperties,另外一個就是通過loadProperties(Properties props)加載本地資源文件作為屬性源的localProperties。
看以下代碼就明白了,
if (this.localOverride) {
this.propertySources.addFirst(localPropertySource);
} else {
this.propertySources.addLast(localPropertySource);
}
解析完占位符得到值以后,出來回到resolveValue方法處,也就是很多if else的方法處,字符串位置
將屬性值原本是${my.property.key}替換成justdoit
到此,對象PropertyPlaceholderBean定義的屬性myPropertyName就被替換成具體的某個值了,這里也就是被替換成了 justdoit
總結:
基于Spring bean的生命周期,BeanFactoryPostProcessor執行方法postProcessBeanFactory,解析獲取到屬性源即environmentProperties以及localProperties兩種,跟著解析占位符,然后得到具體的值,最后set進去替換占位符為具體的屬性值。