'@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?'

XML 文章 人生第一份工作 BigDataKer 2019-07-30
"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

這位小夥伴描述的問題還是蠻清晰,所以我還是很願意跟他一起探討的~

勾起興趣還有一個原因:Spring對佔位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也希望做點努力,能夠起到稍微一點的作用~

對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章裡我就說了這麼一句話:

"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

這位小夥伴描述的問題還是蠻清晰,所以我還是很願意跟他一起探討的~

勾起興趣還有一個原因:Spring對佔位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也希望做點努力,能夠起到稍微一點的作用~

對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章裡我就說了這麼一句話:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

而剛好這個小夥伴的場景(其實我自己還並沒有遇到過此場景),就類屬於老項目到SpringBoot新項目的一個遷移case,這時不結合分析,更待何時呢。

看到聊天記錄,部分小夥伴可能會想:把Bean拿出來配置不就行了嗎?或者key就寫在原來的屬性文件裡唄?

其實對於職場老兵都能對此種現象給與理解和接受,畢竟有種叫理想化,有種叫是叫實際化~

---


因為我不可能貼出該小夥伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大夥看呢?)so,接下來旨在說明這個問題,我就只好採用我的模擬大法嘍:

傳統Spring工程下使用

本處以一個傳統的Spring工程為例,模擬這種使用case。classpath下有如下兩個文件:

spring-beans.xml:

<bean id="myPerson" class="com.fsx.bean.Person">
<property name="name" value="${diy.name}"/>
<property name="age" value="18"/>
</bean>

可以看到此xml配置Bean中使用了佔位符:${diy.name}來引用下面屬性文件的屬性值~

my.properties:

diy.name = fsx-fsx

使用@ImportResource和@PropertySource分別把它哥倆導入:

@ImportResource(locations = "classpath:spring-beans.xml")
@PropertySource("classpath:my.properties")
@Configuration
public class RootConfig {
}

運行如下測試用例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Environment environment;
@Test
public void test1() {
Person bean = (Person) applicationContext.getBean("myPerson");
System.out.println(bean);
System.out.println(environment.getProperty("diy.name"));
}
}

打印結果為:

Person{name='${diy.name}', age=18}
fsx-fsx

從結果中可以至少知道如下兩點:

  1. 環境變量裡是存在diy.name這個屬性k-v的。並且此處我附上截圖如下:
"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

這位小夥伴描述的問題還是蠻清晰,所以我還是很願意跟他一起探討的~

勾起興趣還有一個原因:Spring對佔位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也希望做點努力,能夠起到稍微一點的作用~

對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章裡我就說了這麼一句話:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

而剛好這個小夥伴的場景(其實我自己還並沒有遇到過此場景),就類屬於老項目到SpringBoot新項目的一個遷移case,這時不結合分析,更待何時呢。

看到聊天記錄,部分小夥伴可能會想:把Bean拿出來配置不就行了嗎?或者key就寫在原來的屬性文件裡唄?

其實對於職場老兵都能對此種現象給與理解和接受,畢竟有種叫理想化,有種叫是叫實際化~

---


因為我不可能貼出該小夥伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大夥看呢?)so,接下來旨在說明這個問題,我就只好採用我的模擬大法嘍:

傳統Spring工程下使用

本處以一個傳統的Spring工程為例,模擬這種使用case。classpath下有如下兩個文件:

spring-beans.xml:

<bean id="myPerson" class="com.fsx.bean.Person">
<property name="name" value="${diy.name}"/>
<property name="age" value="18"/>
</bean>

可以看到此xml配置Bean中使用了佔位符:${diy.name}來引用下面屬性文件的屬性值~

my.properties:

diy.name = fsx-fsx

使用@ImportResource和@PropertySource分別把它哥倆導入:

@ImportResource(locations = "classpath:spring-beans.xml")
@PropertySource("classpath:my.properties")
@Configuration
public class RootConfig {
}

運行如下測試用例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Environment environment;
@Test
public void test1() {
Person bean = (Person) applicationContext.getBean("myPerson");
System.out.println(bean);
System.out.println(environment.getProperty("diy.name"));
}
}

打印結果為:

Person{name='${diy.name}', age=18}
fsx-fsx

從結果中可以至少知道如下兩點:

  1. 環境變量裡是存在diy.name這個屬性k-v的。並且此處我附上截圖如下:
@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?


  1. xml中的佔位符並沒有被解析

若你對技術有敏感性的話,你會疑問為何佔位符沒被解析但並沒有報錯呢?

這個問題我在這篇文章:【小家Spring】Spring中@Value註解有多強大?從原理層面去剖析為何它有如此大的“能耐“ 裡有過解釋,有興趣的可以點開看看(沒興趣的可以略過)

存在但又沒被解析,看似有點矛盾,難道Spring工程不支持這麼用,作為職場老兵的你,答案肯定是否定的,那如何破呢?

其實解決起來非常簡單,我們只需要配置上一個PropertyResourceConfigurer即可:

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 使用的PropertySourcesPlaceholderConfigurer,不用自己再手動指定亦可處理佔位符~~~
// configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

再次運行,打印如下:

Person{name='fsx-fsx', age=18}
fsx-fsx

完美~

關於xml配置Bean處理佔位符問題,為了加深理解,亦可參考:【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的

> 我想說:此處介紹的是註解版怎麼處理佔位符問題,若你仍舊是傳統的xml配置項目,至於具體使用哪個標籤,小夥伴自行尋找咯~

我們知道PropertyResourceConfigurer它是個抽象類,它的三大實現子類除了上例使用的,還有其餘兩大實現類:PropertyOverrideConfigurer和PropertyPlaceholderConfigurer,若註冊它哥倆可行嗎??? 行不行試試唄

使用PropertyOverrideConfigurer

PropertyOverrideConfigurer 利用屬性文件的相關信息,覆蓋XML 配置文件中Bean定義。它要求配置的屬性文件第一個.前面是beanName來匹配,所以這個子類我看都不用看,它肯定不行(因為它改變了k-v的結構)。

其實上面說配置PropertyResourceConfigurer的實現類來處理是不太準確的。

準確的說應該是配置PlaceholderConfigurerSupport的實現子類來處理Placeholder佔位符更精確,特此糾正哈~

使用PropertyPlaceholderConfigurer

其實大多數小夥伴對PropertyPlaceholderConfigurer比對PropertySourcesPlaceholderConfigurer熟悉多了,畢竟前者的年紀可大多了~

它哥倆都是PlaceholderConfigurerSupport的實現子類有能力能夠處理佔位符

PropertySourcesPlaceholderConfigurer是Spring 3.1後提供的,希望用來取代PropertyPlaceholderConfigurer

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer();
//configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

運行上面用例就報錯了:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'diy.name' in value "${diy.name}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)

what?看打印結果,明明environment.getProperty("diy.name")從環境裡能拿到這個key呀,怎麼會報錯呢???

這就是為何Spring3.1要提供一個PropertySourcesPlaceholderConfigurer旨在想代替掉此類的原因了。

但是,但是,但是把上配置中注掉的那行代碼放開(也就是說自己設置location從而把屬性文件加載進來),就能正常work了。

關於使用這種方式我還有必要再說明一點:若自己設置了location加載屬性文件,@PropertySource("classpath:my.properties")這句代碼對此種場景就沒有必要了,xml配置的佔位符也是能夠讀取到的。但是但是但是,若注掉這句@PropertySource...,此時運行輸出如下:

Person{name='fsx-fsx', age=18}
null

會發現environment.getProperty("diy.name")為null,也就是說該屬性值並不會存在應用的環境內了(但是xml的佔位符已被成功解析)。從我的這個截圖中也能看出來環境裡已經沒它了:

"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

這位小夥伴描述的問題還是蠻清晰,所以我還是很願意跟他一起探討的~

勾起興趣還有一個原因:Spring對佔位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也希望做點努力,能夠起到稍微一點的作用~

對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章裡我就說了這麼一句話:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

而剛好這個小夥伴的場景(其實我自己還並沒有遇到過此場景),就類屬於老項目到SpringBoot新項目的一個遷移case,這時不結合分析,更待何時呢。

看到聊天記錄,部分小夥伴可能會想:把Bean拿出來配置不就行了嗎?或者key就寫在原來的屬性文件裡唄?

其實對於職場老兵都能對此種現象給與理解和接受,畢竟有種叫理想化,有種叫是叫實際化~

---


因為我不可能貼出該小夥伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大夥看呢?)so,接下來旨在說明這個問題,我就只好採用我的模擬大法嘍:

傳統Spring工程下使用

本處以一個傳統的Spring工程為例,模擬這種使用case。classpath下有如下兩個文件:

spring-beans.xml:

<bean id="myPerson" class="com.fsx.bean.Person">
<property name="name" value="${diy.name}"/>
<property name="age" value="18"/>
</bean>

可以看到此xml配置Bean中使用了佔位符:${diy.name}來引用下面屬性文件的屬性值~

my.properties:

diy.name = fsx-fsx

使用@ImportResource和@PropertySource分別把它哥倆導入:

@ImportResource(locations = "classpath:spring-beans.xml")
@PropertySource("classpath:my.properties")
@Configuration
public class RootConfig {
}

運行如下測試用例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Environment environment;
@Test
public void test1() {
Person bean = (Person) applicationContext.getBean("myPerson");
System.out.println(bean);
System.out.println(environment.getProperty("diy.name"));
}
}

打印結果為:

Person{name='${diy.name}', age=18}
fsx-fsx

從結果中可以至少知道如下兩點:

  1. 環境變量裡是存在diy.name這個屬性k-v的。並且此處我附上截圖如下:
@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?


  1. xml中的佔位符並沒有被解析

若你對技術有敏感性的話,你會疑問為何佔位符沒被解析但並沒有報錯呢?

這個問題我在這篇文章:【小家Spring】Spring中@Value註解有多強大?從原理層面去剖析為何它有如此大的“能耐“ 裡有過解釋,有興趣的可以點開看看(沒興趣的可以略過)

存在但又沒被解析,看似有點矛盾,難道Spring工程不支持這麼用,作為職場老兵的你,答案肯定是否定的,那如何破呢?

其實解決起來非常簡單,我們只需要配置上一個PropertyResourceConfigurer即可:

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 使用的PropertySourcesPlaceholderConfigurer,不用自己再手動指定亦可處理佔位符~~~
// configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

再次運行,打印如下:

Person{name='fsx-fsx', age=18}
fsx-fsx

完美~

關於xml配置Bean處理佔位符問題,為了加深理解,亦可參考:【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的

> 我想說:此處介紹的是註解版怎麼處理佔位符問題,若你仍舊是傳統的xml配置項目,至於具體使用哪個標籤,小夥伴自行尋找咯~

我們知道PropertyResourceConfigurer它是個抽象類,它的三大實現子類除了上例使用的,還有其餘兩大實現類:PropertyOverrideConfigurer和PropertyPlaceholderConfigurer,若註冊它哥倆可行嗎??? 行不行試試唄

使用PropertyOverrideConfigurer

PropertyOverrideConfigurer 利用屬性文件的相關信息,覆蓋XML 配置文件中Bean定義。它要求配置的屬性文件第一個.前面是beanName來匹配,所以這個子類我看都不用看,它肯定不行(因為它改變了k-v的結構)。

其實上面說配置PropertyResourceConfigurer的實現類來處理是不太準確的。

準確的說應該是配置PlaceholderConfigurerSupport的實現子類來處理Placeholder佔位符更精確,特此糾正哈~

使用PropertyPlaceholderConfigurer

其實大多數小夥伴對PropertyPlaceholderConfigurer比對PropertySourcesPlaceholderConfigurer熟悉多了,畢竟前者的年紀可大多了~

它哥倆都是PlaceholderConfigurerSupport的實現子類有能力能夠處理佔位符

PropertySourcesPlaceholderConfigurer是Spring 3.1後提供的,希望用來取代PropertyPlaceholderConfigurer

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer();
//configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

運行上面用例就報錯了:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'diy.name' in value "${diy.name}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)

what?看打印結果,明明environment.getProperty("diy.name")從環境裡能拿到這個key呀,怎麼會報錯呢???

這就是為何Spring3.1要提供一個PropertySourcesPlaceholderConfigurer旨在想代替掉此類的原因了。

但是,但是,但是把上配置中注掉的那行代碼放開(也就是說自己設置location從而把屬性文件加載進來),就能正常work了。

關於使用這種方式我還有必要再說明一點:若自己設置了location加載屬性文件,@PropertySource("classpath:my.properties")這句代碼對此種場景就沒有必要了,xml配置的佔位符也是能夠讀取到的。但是但是但是,若注掉這句@PropertySource...,此時運行輸出如下:

Person{name='fsx-fsx', age=18}
null

會發現environment.getProperty("diy.name")為null,也就是說該屬性值並不會存在應用的環境內了(但是xml的佔位符已被成功解析)。從我的這個截圖中也能看出來環境裡已經沒它了:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

至於這深處到底是什麼原因,有興趣的可以輕點這裡:【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置文件Properties的加載和使用

==只new一個PropertyPlaceholderConfigurer報錯原因分析:==

其實從源代碼處一眼就能看出來原因:

public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport {
...
// 是否能被解析到值,重點在於入參的這個Properties props是否有這個key,而這個參數需要追溯它的父類~
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
doProcessProperties(beanFactoryToProcess, valueResolver);
}
...
}
// 從父類中看看props的傳值是啥~~~
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered {
...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
// 抽象方法,交給子類~~~這裡傳入的mergedProps,全部來自location~~~
processProperties(beanFactory, mergedProps);
} catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
protected Properties mergeProperties() throws IOException {
...
loadProperties(result);
...
}
// 從配置裡的location裡把屬性值都讀出來~~~~~
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
...
PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
...
}
}
}
...
}

由此可見,若上述@Bean配置使用的是PropertyPlaceholderConfigurer,那必須手動的把屬性文件設置location加載進去才行,否則是讀取不到滴~

==那麼問題來了,為何使用PropertySourcesPlaceholderConfigurer,只需要簡單的new一個就成了勒(並不需要手動設置location)?==

一樣的,從源碼處一看便知,非常非常簡單:

// @since 3.1 直接實現了EnvironmentAware,說明此Bean可以拿到當前環境Environment
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
...
// 把環境屬性都放進來~
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
@Nullable
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
// 把配置的屬性放進來~~~
PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
this.propertySources.addFirst(localPropertySource);
...
}
}

相信不用我做過多的解釋,就知道為何不用自己設置location,直接使用註解@PropertySource("classpath:my.properties")就好使了吧。這個時候環境截圖如下(注意:此處我截圖是基於已經set了location的截圖哦):

"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

這位小夥伴描述的問題還是蠻清晰,所以我還是很願意跟他一起探討的~

勾起興趣還有一個原因:Spring對佔位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也希望做點努力,能夠起到稍微一點的作用~

對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章裡我就說了這麼一句話:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

而剛好這個小夥伴的場景(其實我自己還並沒有遇到過此場景),就類屬於老項目到SpringBoot新項目的一個遷移case,這時不結合分析,更待何時呢。

看到聊天記錄,部分小夥伴可能會想:把Bean拿出來配置不就行了嗎?或者key就寫在原來的屬性文件裡唄?

其實對於職場老兵都能對此種現象給與理解和接受,畢竟有種叫理想化,有種叫是叫實際化~

---


因為我不可能貼出該小夥伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大夥看呢?)so,接下來旨在說明這個問題,我就只好採用我的模擬大法嘍:

傳統Spring工程下使用

本處以一個傳統的Spring工程為例,模擬這種使用case。classpath下有如下兩個文件:

spring-beans.xml:

<bean id="myPerson" class="com.fsx.bean.Person">
<property name="name" value="${diy.name}"/>
<property name="age" value="18"/>
</bean>

可以看到此xml配置Bean中使用了佔位符:${diy.name}來引用下面屬性文件的屬性值~

my.properties:

diy.name = fsx-fsx

使用@ImportResource和@PropertySource分別把它哥倆導入:

@ImportResource(locations = "classpath:spring-beans.xml")
@PropertySource("classpath:my.properties")
@Configuration
public class RootConfig {
}

運行如下測試用例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Environment environment;
@Test
public void test1() {
Person bean = (Person) applicationContext.getBean("myPerson");
System.out.println(bean);
System.out.println(environment.getProperty("diy.name"));
}
}

打印結果為:

Person{name='${diy.name}', age=18}
fsx-fsx

從結果中可以至少知道如下兩點:

  1. 環境變量裡是存在diy.name這個屬性k-v的。並且此處我附上截圖如下:
@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?


  1. xml中的佔位符並沒有被解析

若你對技術有敏感性的話,你會疑問為何佔位符沒被解析但並沒有報錯呢?

這個問題我在這篇文章:【小家Spring】Spring中@Value註解有多強大?從原理層面去剖析為何它有如此大的“能耐“ 裡有過解釋,有興趣的可以點開看看(沒興趣的可以略過)

存在但又沒被解析,看似有點矛盾,難道Spring工程不支持這麼用,作為職場老兵的你,答案肯定是否定的,那如何破呢?

其實解決起來非常簡單,我們只需要配置上一個PropertyResourceConfigurer即可:

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 使用的PropertySourcesPlaceholderConfigurer,不用自己再手動指定亦可處理佔位符~~~
// configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

再次運行,打印如下:

Person{name='fsx-fsx', age=18}
fsx-fsx

完美~

關於xml配置Bean處理佔位符問題,為了加深理解,亦可參考:【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的

> 我想說:此處介紹的是註解版怎麼處理佔位符問題,若你仍舊是傳統的xml配置項目,至於具體使用哪個標籤,小夥伴自行尋找咯~

我們知道PropertyResourceConfigurer它是個抽象類,它的三大實現子類除了上例使用的,還有其餘兩大實現類:PropertyOverrideConfigurer和PropertyPlaceholderConfigurer,若註冊它哥倆可行嗎??? 行不行試試唄

使用PropertyOverrideConfigurer

PropertyOverrideConfigurer 利用屬性文件的相關信息,覆蓋XML 配置文件中Bean定義。它要求配置的屬性文件第一個.前面是beanName來匹配,所以這個子類我看都不用看,它肯定不行(因為它改變了k-v的結構)。

其實上面說配置PropertyResourceConfigurer的實現類來處理是不太準確的。

準確的說應該是配置PlaceholderConfigurerSupport的實現子類來處理Placeholder佔位符更精確,特此糾正哈~

使用PropertyPlaceholderConfigurer

其實大多數小夥伴對PropertyPlaceholderConfigurer比對PropertySourcesPlaceholderConfigurer熟悉多了,畢竟前者的年紀可大多了~

它哥倆都是PlaceholderConfigurerSupport的實現子類有能力能夠處理佔位符

PropertySourcesPlaceholderConfigurer是Spring 3.1後提供的,希望用來取代PropertyPlaceholderConfigurer

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer();
//configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

運行上面用例就報錯了:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'diy.name' in value "${diy.name}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)

what?看打印結果,明明environment.getProperty("diy.name")從環境裡能拿到這個key呀,怎麼會報錯呢???

這就是為何Spring3.1要提供一個PropertySourcesPlaceholderConfigurer旨在想代替掉此類的原因了。

但是,但是,但是把上配置中注掉的那行代碼放開(也就是說自己設置location從而把屬性文件加載進來),就能正常work了。

關於使用這種方式我還有必要再說明一點:若自己設置了location加載屬性文件,@PropertySource("classpath:my.properties")這句代碼對此種場景就沒有必要了,xml配置的佔位符也是能夠讀取到的。但是但是但是,若注掉這句@PropertySource...,此時運行輸出如下:

Person{name='fsx-fsx', age=18}
null

會發現environment.getProperty("diy.name")為null,也就是說該屬性值並不會存在應用的環境內了(但是xml的佔位符已被成功解析)。從我的這個截圖中也能看出來環境裡已經沒它了:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

至於這深處到底是什麼原因,有興趣的可以輕點這裡:【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置文件Properties的加載和使用

==只new一個PropertyPlaceholderConfigurer報錯原因分析:==

其實從源代碼處一眼就能看出來原因:

public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport {
...
// 是否能被解析到值,重點在於入參的這個Properties props是否有這個key,而這個參數需要追溯它的父類~
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
doProcessProperties(beanFactoryToProcess, valueResolver);
}
...
}
// 從父類中看看props的傳值是啥~~~
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered {
...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
// 抽象方法,交給子類~~~這裡傳入的mergedProps,全部來自location~~~
processProperties(beanFactory, mergedProps);
} catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
protected Properties mergeProperties() throws IOException {
...
loadProperties(result);
...
}
// 從配置裡的location裡把屬性值都讀出來~~~~~
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
...
PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
...
}
}
}
...
}

由此可見,若上述@Bean配置使用的是PropertyPlaceholderConfigurer,那必須手動的把屬性文件設置location加載進去才行,否則是讀取不到滴~

==那麼問題來了,為何使用PropertySourcesPlaceholderConfigurer,只需要簡單的new一個就成了勒(並不需要手動設置location)?==

一樣的,從源碼處一看便知,非常非常簡單:

// @since 3.1 直接實現了EnvironmentAware,說明此Bean可以拿到當前環境Environment
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
...
// 把環境屬性都放進來~
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
@Nullable
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
// 把配置的屬性放進來~~~
PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
this.propertySources.addFirst(localPropertySource);
...
}
}

相信不用我做過多的解釋,就知道為何不用自己設置location,直接使用註解@PropertySource("classpath:my.properties")就好使了吧。這個時候環境截圖如下(注意:此處我截圖是基於已經set了location的截圖哦):

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

what?雖然配置時候set了location去加載屬性文件,但是上面代碼中add進去的屬性源environmentProperties和localProperties

public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";
public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";

兩個PropertySource都並沒有出現?

關於此,我這裡就不再解釋了,哈哈。還是那句話,留給小夥伴們自己思考,若思考不明白的亦可掃碼入群探討哦~(當然參照上面文章也是可行的)


SpringBoot工程下使用

關於在SpringBoot中使用,簡單到令人髮指,畢竟SpringBoot的使命也是讓你使用它能夠簡單到木有朋友。

so,在SpringBoot工程下使用@ImportResource和@PropertySource啥都不用配,它是能夠天然的直接work的~

==原因分析如下:==

一切得益於SpringBoot強悍的自動化配置能力,它提供了這樣一個配置類:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 最高優先級
public class PropertyPlaceholderAutoConfiguration {
// 注意此處使用的是PropertySourcesPlaceholderConfigurer
// 並且你可以在本容器內覆蓋它的默認行為喲~~~~
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

PropertyPlaceholderAutoConfiguration它被配置在了自動配置包下的spring.factories文件裡:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
...
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\\
...

因此它會隨著工程啟動而自動生效。有了上面對Spring工程下的使用分析,此處就不用再花筆墨解釋了~

另外附加說明一點:哪怕你的屬性不使用@PropertySource導入,而是寫在SB自帶的application.properties文件裡,依舊是沒有問題的。

==原因分析如下:==

其實這個涉及到的是SpringBoot對application.properties的加載時機問題,因為本文對SB的介紹並不是重點,因此此處我直接簡單的說出結論即止:

  • SpringBoot通過事件監聽機制加載很多東西,加載此屬性文件用的是ConfigFileApplicationListener這個監聽器
  • 它監聽到ApplicationEnvironmentPreparedEvent(環境準備好後)事件後開始加載application.properties等文件
  • ApplicationEnvironmentPreparedEvent的事件發出是發生在createApplicationContext()之前~~~ 部分代碼如下:
public class SpringApplication {
...
public ConfigurableApplicationContext run(String... args) {
...
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 此步驟 會發出ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = printBanner(environment);

// 開始創建,初始化容器~
context = createApplicationContext();
...
}
...
}

so,在SB環境下已經早早把屬性都放進環境內了,藉助它默認配置好的PropertySourcesPlaceholderConfigurer來處理的,那可不能正常work嗎。一切都是這麼自然,這或許就是SB的魅力所在吧~

關於小夥伴問題的解決

開頭提出了問題,肯定得解決問題嘛~~~如下圖

"

前言

寫這篇文章的原動力是由於昨晚深夜一個小夥伴諮詢我的一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。

當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你後續或許有所幫助~

情景描述

為了更直觀的說明問題所在,截圖部分聊天記錄如下:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

這位小夥伴描述的問題還是蠻清晰,所以我還是很願意跟他一起探討的~

勾起興趣還有一個原因:Spring對佔位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也希望做點努力,能夠起到稍微一點的作用~

對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章裡我就說了這麼一句話:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

而剛好這個小夥伴的場景(其實我自己還並沒有遇到過此場景),就類屬於老項目到SpringBoot新項目的一個遷移case,這時不結合分析,更待何時呢。

看到聊天記錄,部分小夥伴可能會想:把Bean拿出來配置不就行了嗎?或者key就寫在原來的屬性文件裡唄?

其實對於職場老兵都能對此種現象給與理解和接受,畢竟有種叫理想化,有種叫是叫實際化~

---


因為我不可能貼出該小夥伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大夥看呢?)so,接下來旨在說明這個問題,我就只好採用我的模擬大法嘍:

傳統Spring工程下使用

本處以一個傳統的Spring工程為例,模擬這種使用case。classpath下有如下兩個文件:

spring-beans.xml:

<bean id="myPerson" class="com.fsx.bean.Person">
<property name="name" value="${diy.name}"/>
<property name="age" value="18"/>
</bean>

可以看到此xml配置Bean中使用了佔位符:${diy.name}來引用下面屬性文件的屬性值~

my.properties:

diy.name = fsx-fsx

使用@ImportResource和@PropertySource分別把它哥倆導入:

@ImportResource(locations = "classpath:spring-beans.xml")
@PropertySource("classpath:my.properties")
@Configuration
public class RootConfig {
}

運行如下測試用例:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private Environment environment;
@Test
public void test1() {
Person bean = (Person) applicationContext.getBean("myPerson");
System.out.println(bean);
System.out.println(environment.getProperty("diy.name"));
}
}

打印結果為:

Person{name='${diy.name}', age=18}
fsx-fsx

從結果中可以至少知道如下兩點:

  1. 環境變量裡是存在diy.name這個屬性k-v的。並且此處我附上截圖如下:
@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?


  1. xml中的佔位符並沒有被解析

若你對技術有敏感性的話,你會疑問為何佔位符沒被解析但並沒有報錯呢?

這個問題我在這篇文章:【小家Spring】Spring中@Value註解有多強大?從原理層面去剖析為何它有如此大的“能耐“ 裡有過解釋,有興趣的可以點開看看(沒興趣的可以略過)

存在但又沒被解析,看似有點矛盾,難道Spring工程不支持這麼用,作為職場老兵的你,答案肯定是否定的,那如何破呢?

其實解決起來非常簡單,我們只需要配置上一個PropertyResourceConfigurer即可:

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 使用的PropertySourcesPlaceholderConfigurer,不用自己再手動指定亦可處理佔位符~~~
// configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

再次運行,打印如下:

Person{name='fsx-fsx', age=18}
fsx-fsx

完美~

關於xml配置Bean處理佔位符問題,為了加深理解,亦可參考:【小家Spring】Spring IoC是如何使用BeanWrapper和Java內省結合起來給Bean屬性賦值的

> 我想說:此處介紹的是註解版怎麼處理佔位符問題,若你仍舊是傳統的xml配置項目,至於具體使用哪個標籤,小夥伴自行尋找咯~

我們知道PropertyResourceConfigurer它是個抽象類,它的三大實現子類除了上例使用的,還有其餘兩大實現類:PropertyOverrideConfigurer和PropertyPlaceholderConfigurer,若註冊它哥倆可行嗎??? 行不行試試唄

使用PropertyOverrideConfigurer

PropertyOverrideConfigurer 利用屬性文件的相關信息,覆蓋XML 配置文件中Bean定義。它要求配置的屬性文件第一個.前面是beanName來匹配,所以這個子類我看都不用看,它肯定不行(因為它改變了k-v的結構)。

其實上面說配置PropertyResourceConfigurer的實現類來處理是不太準確的。

準確的說應該是配置PlaceholderConfigurerSupport的實現子類來處理Placeholder佔位符更精確,特此糾正哈~

使用PropertyPlaceholderConfigurer

其實大多數小夥伴對PropertyPlaceholderConfigurer比對PropertySourcesPlaceholderConfigurer熟悉多了,畢竟前者的年紀可大多了~

它哥倆都是PlaceholderConfigurerSupport的實現子類有能力能夠處理佔位符

PropertySourcesPlaceholderConfigurer是Spring 3.1後提供的,希望用來取代PropertyPlaceholderConfigurer

 @Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer();
//configurer.setLocation(new ClassPathResource("my.properties")); // 加載指定的屬性文件
return configurer;
}

運行上面用例就報錯了:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'diy.name' in value "${diy.name}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)

what?看打印結果,明明environment.getProperty("diy.name")從環境裡能拿到這個key呀,怎麼會報錯呢???

這就是為何Spring3.1要提供一個PropertySourcesPlaceholderConfigurer旨在想代替掉此類的原因了。

但是,但是,但是把上配置中注掉的那行代碼放開(也就是說自己設置location從而把屬性文件加載進來),就能正常work了。

關於使用這種方式我還有必要再說明一點:若自己設置了location加載屬性文件,@PropertySource("classpath:my.properties")這句代碼對此種場景就沒有必要了,xml配置的佔位符也是能夠讀取到的。但是但是但是,若注掉這句@PropertySource...,此時運行輸出如下:

Person{name='fsx-fsx', age=18}
null

會發現environment.getProperty("diy.name")為null,也就是說該屬性值並不會存在應用的環境內了(但是xml的佔位符已被成功解析)。從我的這個截圖中也能看出來環境裡已經沒它了:

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

至於這深處到底是什麼原因,有興趣的可以輕點這裡:【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置文件Properties的加載和使用

==只new一個PropertyPlaceholderConfigurer報錯原因分析:==

其實從源代碼處一眼就能看出來原因:

public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport {
...
// 是否能被解析到值,重點在於入參的這個Properties props是否有這個key,而這個參數需要追溯它的父類~
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
doProcessProperties(beanFactoryToProcess, valueResolver);
}
...
}
// 從父類中看看props的傳值是啥~~~
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered {
...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
// 抽象方法,交給子類~~~這裡傳入的mergedProps,全部來自location~~~
processProperties(beanFactory, mergedProps);
} catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
protected Properties mergeProperties() throws IOException {
...
loadProperties(result);
...
}
// 從配置裡的location裡把屬性值都讀出來~~~~~
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
...
PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
...
}
}
}
...
}

由此可見,若上述@Bean配置使用的是PropertyPlaceholderConfigurer,那必須手動的把屬性文件設置location加載進去才行,否則是讀取不到滴~

==那麼問題來了,為何使用PropertySourcesPlaceholderConfigurer,只需要簡單的new一個就成了勒(並不需要手動設置location)?==

一樣的,從源碼處一看便知,非常非常簡單:

// @since 3.1 直接實現了EnvironmentAware,說明此Bean可以拿到當前環境Environment
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
...
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
...
// 把環境屬性都放進來~
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
@Nullable
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
// 把配置的屬性放進來~~~
PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
this.propertySources.addFirst(localPropertySource);
...
}
}

相信不用我做過多的解釋,就知道為何不用自己設置location,直接使用註解@PropertySource("classpath:my.properties")就好使了吧。這個時候環境截圖如下(注意:此處我截圖是基於已經set了location的截圖哦):

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

what?雖然配置時候set了location去加載屬性文件,但是上面代碼中add進去的屬性源environmentProperties和localProperties

public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";
public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";

兩個PropertySource都並沒有出現?

關於此,我這裡就不再解釋了,哈哈。還是那句話,留給小夥伴們自己思考,若思考不明白的亦可掃碼入群探討哦~(當然參照上面文章也是可行的)


SpringBoot工程下使用

關於在SpringBoot中使用,簡單到令人髮指,畢竟SpringBoot的使命也是讓你使用它能夠簡單到木有朋友。

so,在SpringBoot工程下使用@ImportResource和@PropertySource啥都不用配,它是能夠天然的直接work的~

==原因分析如下:==

一切得益於SpringBoot強悍的自動化配置能力,它提供了這樣一個配置類:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 最高優先級
public class PropertyPlaceholderAutoConfiguration {
// 注意此處使用的是PropertySourcesPlaceholderConfigurer
// 並且你可以在本容器內覆蓋它的默認行為喲~~~~
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}

PropertyPlaceholderAutoConfiguration它被配置在了自動配置包下的spring.factories文件裡:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
...
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\\
...

因此它會隨著工程啟動而自動生效。有了上面對Spring工程下的使用分析,此處就不用再花筆墨解釋了~

另外附加說明一點:哪怕你的屬性不使用@PropertySource導入,而是寫在SB自帶的application.properties文件裡,依舊是沒有問題的。

==原因分析如下:==

其實這個涉及到的是SpringBoot對application.properties的加載時機問題,因為本文對SB的介紹並不是重點,因此此處我直接簡單的說出結論即止:

  • SpringBoot通過事件監聽機制加載很多東西,加載此屬性文件用的是ConfigFileApplicationListener這個監聽器
  • 它監聽到ApplicationEnvironmentPreparedEvent(環境準備好後)事件後開始加載application.properties等文件
  • ApplicationEnvironmentPreparedEvent的事件發出是發生在createApplicationContext()之前~~~ 部分代碼如下:
public class SpringApplication {
...
public ConfigurableApplicationContext run(String... args) {
...
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 此步驟 會發出ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = printBanner(environment);

// 開始創建,初始化容器~
context = createApplicationContext();
...
}
...
}

so,在SB環境下已經早早把屬性都放進環境內了,藉助它默認配置好的PropertySourcesPlaceholderConfigurer來處理的,那可不能正常work嗎。一切都是這麼自然,這或許就是SB的魅力所在吧~

關於小夥伴問題的解決

開頭提出了問題,肯定得解決問題嘛~~~如下圖

@ImportResource導入xml裡Bean能用@PropertySource導入屬性值?

哈哈,雖然最終我並沒有直接的幫助解決問題,但是此問題給了我寫本文的動力,總體還是不錯的~

總結

本文通過一個小夥伴諮詢的小問題(真是小問題嗎?)引申比較詳細的說了Spring在處理佔位符這塊的內容(其實本並沒打算寫這麼多的,尷尬~)

寫本文的目的開頭也說了,我認為在SpringBoot還並非100%滲透的當下,肯定有人會遇到從傳統Spring項目向SpringBoot過度的一個階段,而本文的描述或許能給你的遷移提供一種新的思路(特別是時間緊、任務重的時候),希望小夥伴們能有所收穫,peace~

作者:_YourBatman

原文:https://www.cnblogs.com/fangshixiang/p/11212210.html

"