摘要:關于使用這種方式我還有必要再說明一點若自己設置了加載屬性文件,這句代碼對此種場景就沒有必要了,配置的占位符也是能夠讀取到的。
每篇一句
大師都是偏執的,偏執才能產生力量,妥協是沒有力量的。你對全世界妥協了你就是空氣。所以若沒有偏見,哪來的大師呢相關閱讀
【小家Spring】詳解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等對屬性配置文件Properties的加載和使用
【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析
【小家Spring】Spring中@Value注解有多強大?從原理層面去剖析為何它有如此大的“能耐“
寫這篇文章的原動力是由于昨晚深夜一個小伙伴咨詢我的一個問題(這位小伙伴這么晚了還在折騰,也是給個大大的贊),涉及到了如題方面的知識。
當然促使我書寫本文最重要原因的是:這種從傳統Spring項目向SpringBoot遷移進階的case,我個人認為在現階段的環境下還是有較大概率出現的,因此推薦收藏本文,對你后續或許有所幫助~
為了更直觀的說明問題所在,截圖部分聊天記錄如下:
這位小伙伴描述的問題還是蠻清晰,所以我還是很愿意跟他一起探討的~
勾起興趣還有一個原因:Spring對占位符提供了非常強大的支持,但基本上新手都還不能好好利用它和利用好它,更區分不清使用的規范和區別,本文也希望做點努力,能夠起到稍微一點的作用~
對此部分內容若需要熱場,推薦可以先瀏覽一下這篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的區別,以及各自的實現原理解析 可以看到,早在我這篇文章里我就說了這么一句話:
而剛好這個小伙伴的場景(其實我自己還并沒有遇到過此場景),就類屬于老項目到SpringBoot新項目的一個遷移case,這時不結合分析,更待何時呢。
看到聊天記錄,部分小伙伴可能會想:把Bean拿出來配置不就行了嗎?或者key就寫在原來的屬性文件里唄?
其實對于職場老兵都能對此種現象給與理解和接受,畢竟有種叫理想化,有種叫是叫實際化~
因為我不可能貼出該小伙伴的源碼(畢竟人家是生產環境的代碼,咋可能貼出給大伙看呢?)so,接下來旨在說明這個問題,我就只好采用我的模擬大法嘍:
傳統Spring工程下使用本處以一個傳統的Spring工程為例,模擬這種使用case。classpath下有如下兩個文件:
spring-beans.xml:
可以看到此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
從結果中可以至少知道如下兩點:
環境變量里是存在diy.name這個屬性k-v的。并且此處我附上截圖如下:
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 利用屬性文件的相關信息,覆蓋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_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入群" 字樣,會手動邀請入群**
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75481.html
摘要:創建及準備創建。目前已知關心這個事件的有要注意的是在這個階段,里只有,是的加載工作的起點。原因是注入這些回調接口本身沒有什么意義。在其構造函數內部間接的給注冊了幾個與相關注解的處理器。 相關代碼在: https://github.com/chanjarster/spring-boot-all-callbacks 注:本文基于spring-boot 1.4.1.RELEASE, spri...
摘要:如刪除臨時文件,清除緩存信息,讀取配置文件信息,數據庫連接等。提供的接口也可以滿足該業務場景。不同點中方法的參數為,而接口中方法的參數為數組。 spring-boot-starter-parent Maven的用戶可以通過繼承spring-boot-starter-parent項目來獲得一些合理的默認配置。這個parent提供了以下特性: 默認使用Java 8 使用UTF-8編碼 一...
摘要:用法先創建個組件,,,分別在類上加上注解。發現有一個屬性源碼注釋這樣說的自動檢測使用組件。在的方法中,表示不匹配,代表匹配。這就說明使用注冊組件有種方式。 Spring注解應用篇--IOC容器Bean組件注冊 這是Spring注解專題系類文章,本系類文章適合Spring入門者或者原理入門者,小編會在本系類文章下進行企業級應用實戰講解以及spring源碼跟進。 環境準備 編譯器IDEA...
摘要:簡單的說,就通過具體就是類作為入口,配合注解實現了自動配置。注解讓自動配置生效。接下來,從開始分析。這個同名方法中,創建了一個對象并調用了對象的成員方法。是類的對象,的方法中有解析的主要邏輯。分別對注解注解注解注解注解注解進行解析。 Springboot使用的版本是2.1.6.RELEASE。簡單的說,Springboot就通過BeanFactoryPostProcessor(具體就是...
摘要:許多配置示例已經在上發布,它們使用配置。逐漸取代自動配置自動配置非侵入性,在任何時候,你都可以開始定義自己的配置來替換自動配置的特定部分。最后,你還可以通過使用屬性來控制要排除的自動配置類的列表。 15. 配置類 Spring Boot支持基于java的配置,雖然可以使用XML源的SpringApplication,但是我們通常建議你的主源是一個@Configuration類。通常,定...
閱讀 3943·2021-09-22 10:02
閱讀 3371·2019-08-30 15:52
閱讀 3064·2019-08-30 12:51
閱讀 759·2019-08-30 11:08
閱讀 2069·2019-08-29 15:18
閱讀 3110·2019-08-29 12:13
閱讀 3598·2019-08-29 11:29
閱讀 1876·2019-08-29 11:13