大師都是偏執的,偏執才能產生力量,妥協是沒有力量的。你對全世界妥協了你就是空氣。因此若沒有偏見,哪來的大師呢
【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置文件Properties的加載和使用
【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析java
【小家Spring】Spring中@Value註解有多強大?從原理層面去剖析爲什麼它有如此大的「能耐「spring
<center>對Spring感興趣可掃碼加入wx羣:Java高工、架構師3羣
(文末有二維碼)</center>架構
寫這篇文章的原動力是因爲昨晚深夜一個小夥伴諮詢個人一個問題(這位小夥伴這麼晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。
固然促使我書寫本文最重要緣由的是:這種從傳統Spring
項目向SpringBoot
遷移進階的case,我我的認爲在現階段的環境下仍是有較大機率出現的,所以推薦收藏本文,對你後續或許有所幫助~app
爲了更直觀的說明問題所在,截圖部分聊天記錄以下:
這位小夥伴描述的問題仍是蠻清晰,因此我仍是很願意跟他一塊兒探討的~ide
勾起興趣還有一個緣由:Spring
對佔位符提供了很是強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規範和區別,本文也但願作點努力,可以起到稍微一點的做用~
對此部份內容若須要熱場
,推薦能夠先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 能夠看到,早在我這篇文章裏我就說了這麼一句話:
而恰好這個小夥伴的場景(其實我本身還並無遇到過此場景),就類屬於老項目
到SpringBoot新項目
的一個遷移case,這時不結合分析,更待什麼時候呢。post
看到聊天記錄,部分小夥伴可能會想:把Bean拿出來配置不就好了嗎?或者key就寫在原來的屬性文件裏唄?
其實對於職場老兵都能對此種現象給與理解和接受,畢竟有種叫 理想化,有種叫是叫 實際化~
由於我不可能貼出該小夥伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大夥看呢?)so,接下來旨在說明這個問題,我就只好採用個人模擬大法嘍:測試
本處以一個傳統的Spring工程爲例,模擬這種使用case。classpath
下有以下兩個文件:
spring-beans.xml:this
<bean id="myPerson" class="com.fsx.bean.Person"> <property name="name" value="${diy.name}"/> <property name="age" value="18"/> </bean>
能夠看到此xml配置Bean中使用了佔位符:${diy.name}
來引用下面屬性文件的屬性值~spa
my.properties:.net
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
從結果中能夠至少知道以下兩點:
diy.name
這個屬性k-v的。而且此處我附上截圖以下:
若你對技術有敏感性的話,你會疑問 爲什麼佔位符沒被解析但並無報錯呢?
這個問題我在這篇文章: 【小家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
利用屬性文件的相關信息,覆蓋XML 配置文件中Bean定義
。它要求配置的屬性文件第一個.
前面是beanName
來匹配,因此這個子類我看都不用看,它確定不行(由於它改變了k-v的結構)。
**其實上面說配置PropertyResourceConfigurer
的實現類來處理是不太準確的。
準確的說應該是配置PlaceholderConfigurerSupport
的實現子類來處理Placeholder佔位符
更精確,特此糾正哈~**
其實大多數小夥伴對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】詳解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
的截圖哦):
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
的使命也是讓你使用它可以簡單到木有朋友。
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
還並不是100%滲透的當下,確定有人會遇到從傳統Spring
項目向SpringBoot
過分的一個階段,而本文的描述或許能給你的遷移提供一種新的思路(特別是時間緊、任務重的時候),但願小夥伴們能有所收穫,peace~
若文章格式混亂,可點擊
:
原文連接-原文連接-原文連接-原文連接-原文連接
==The last:若是以爲本文對你有幫助,不妨點個讚唄。固然分享到你的朋友圈讓更多小夥伴看到也是被做者本人許可的~
==
**若對技術內容感興趣能夠加入wx羣交流:Java高工、架構師3羣
。
若羣二維碼失效,請加wx號:fsx641385712
(或者掃描下方wx二維碼)。而且備註:"java入羣"
字樣,會手動邀請入羣**