深度解析@PropertySource注解
一、學習指引
@PropertySource注解是用來干啥的呢?
在日常開發中,你有沒有遇到過這樣一種場景:項目中需要編寫很多配置文件,將一些系統信息配置化,此時,往往需要編寫專門的工具類或者方法來讀取并解析這些配置文件,將配置文件中的配置項內容加載到系統內存中。后續在使用這些配置項時,可以直接通過工具類或者方法獲取加載到內存中的配置項。
沒錯,@PropertySource注解就是Spring中提供的一個可以加載配置文件的注解,并且可以將配置文件中的內容存放到Spring的環境變量中。
二、注解說明
簡單介紹下@PropertySource注解吧!
@PropertySource注解是Spring中提供的一個通過指定配置文件位置來加載配置文件的注解,并且可以將配置文件中的內容存放到Spring的環境變量中。除了可以通過Spring的環境變量讀取配置項之外,還可以通過@Value注解獲取配置項的值。
另外,Spring中還提供了一個@PropertySources注解,在@PropertySources注解注解中,可以引入多個@PropertySource注解。
1、注解源碼
Spring中提供了@PropertySource和@PropertySources兩個注解來加載配置文件。
(1)@PropertySource注解
@PropertySource注解只能標注到類上,能夠通過指定配置文件的位置來加載配置文件,@PropertySource注解除了可以加載properties配置文件外,也可以加載xml配置文件和yml配置文件。如果加載yml配置文件時,可以自定義PropertySourceFactory實現yml配置文件的解析操作。
@PropertySource注解的源碼詳見:org.springframework.context.annotation.PropertySource。
從源碼可以看出,@PropertySource注解是從Spring3.1版本開始提供的注解,注解中各個屬性的含義如下所示。
- name:表示加載的資源的名稱,如果為空,則會根據加載的配置文件自動生成一個名稱。
- value:表示加載的資源的路徑,這個路徑可以是類路徑,也可以是文件路徑。
- ignoreResourceNotFound:表示當配置文件未找到時,是否忽略文件未找到的錯誤。默認值為false,也就是說當未找到配置文件時,Spring啟動就會報錯。
- encoding:表示解析配置文件使用的字符集編碼。
- factory:表示讀取對應配置文件的工廠類,默認的工廠類是PropertySourceFactory。
(2)@PropertySources注解
除了@PropertySource注解,Spring中還提供了一個@PropertySources注解。@PropertySources注解中的源碼詳見:org.springframework.context.annotation.PropertySources。
從源碼可以看出,@PropertySources是從Spring4.0版本開始提供的注解,在@PropertySources注解中,只提供了一個PropertySource數組類型的value屬性。所以,@PropertySources注解可以引入多個@PropertySource注解。
2、注解使用場景
在基于Spring的注解開發項目的過程中,由于不再使用Spring的XML文件進行配置,如果將配置項直接寫到類中,就會造成配置項與類的緊耦合,后續對于配置項的修改操作非常不方便,不利于項目的維護和擴展。此時,可以將這些配置項寫到properties文件或者yml文件中,通過@PropertySource注解加載配置文件。
另外,如果項目本身就存在大量的properties配置文件或者yml配置文件,也可以統一由Spring的@PropertySource注解進行加載。
三、使用案例
結合案例學著印象才會更深刻~~
本節,主要實現一個通過@PropertySource注解加載properties配置文件,將properties配置文件中的配置項加載到Spring的環境變量中,獲取Spring環境變量中配置項的值,并進行打印。案例的具體實現步驟如下所示。
1、新增test.properties文件
在spring-annotation-chapter-06工程的resources目錄下新增test.properties文件,文件內容如下所示。
2、新增PropertySourceConfig類
PropertySourceConfig類的源碼詳見:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.config.PropertySourceConfig。
可以看到,PropertySourceConfig類是Spring的配置類,并且使用@PropertySource注解指定了test.properties配置文件的路徑。
3、新增PropertySourceTest類
PropertySourceTest類的源碼詳見:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.PropertySourceTest。
可以看到,在PropertySourceTest類的main()方法中,通過AnnotationConfigApplicationContext類的對象獲取到ConfigurableEnvironment類型的環境變量對象environment,然后通過environment對象獲取配置文件中的name和age的值并進行打印。
4、運行PropertySourceTest類
運行PropertySourceTest類的main()方法,輸出的結果信息如下所示。
可以看到,正確的輸出了配置文件中的值。
說明:使用@PropertySource注解可以加載properties配置文件中的配置項,并將配置項加載到Spring的環境變量中,通過Spring的環境變量就可以獲取到配置項的值。
四、源碼時序圖
結合時序圖理解源碼會事半功倍,你覺得呢?
本節,就以源碼時序圖的方式,直觀的感受下@PropertySource注解在Spring源碼層面的執行流程。@PropertySource注解在Spring源碼層面的執行流程如圖6-1~6-2所示。
圖6-1
圖6-2
由圖6-1~圖6-2可以看出,@PropertySource注解在Spring源碼層面的執行流程會涉及到PropertySourceTest類、AnnotationConfigApplicationContext類、AbstractApplicationContext類、PostProcessorRegistrationDelegate類、ConfigurationClassPostProcessor類、ConfigurationClassParser類、PropertySourceRegistry類、PropertySourceProcessor類和DefaultPropertySourceFactory類。具體的源碼執行細節參見源碼解析部分。
五、源碼解析
源碼時序圖整清楚了,那就整源碼解析唄!
@PropertySource注解在Spring源碼層面的執行流程,結合源碼執行的時序圖,會理解的更加深刻。
(1)運行案例程序啟動類。
案例程序啟動類源碼詳見:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.PropertySourceTest,運行PropertySourceTest類的main()方法。
在PropertySourceTest類的main()方法中調用了AnnotationConfigApplicationContext類的構造方法,并傳入了PropertySourceConfig類的Class對象來創建IOC容器。接下來,會進入AnnotationConfigApplicationContext類的構造方法。
注意:@PropertySource注解在Spring源碼中的執行流程的(2)~(11)步與第5章的@Import注解相同,這里不再贅述,直接跳到ConfigurationClassParser類的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter)方法。
(2)解析ConfigurationClassParser類的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter)方法。
源碼詳見:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter),重點關注如下代碼片段。
可以看到,在ConfigurationClassParser類的doProcessConfigurationClass()方法中,遍歷獲取到的@PropertySources注解和@PropertySource注解的屬性,并且調用propertySourceRegistry對象的processPropertySource()方法解析注解屬性的值。
(3)解析PropertySourceRegistry類的processPropertySource(AnnotationAttributes propertySource)方法。
源碼詳見:org.springframework.context.annotation.PropertySourceRegistry#processPropertySource(AnnotationAttributes propertySource)。
可以看到,在PropertySourceRegistry類的processPropertySource()方法中,解析@PropertySource注解中的屬性后,將解析出的屬性值封裝到PropertySourceDescriptor對象中,調用propertySourceProcessor對象的processPropertySource()方法,并傳入PropertySourceDescriptor對象進行進一步處理。
(4)解析PropertySourceProcessor類的processPropertySource(PropertySourceDescriptor descriptor)方法。
源碼詳見:org.springframework.core.io.support.PropertySourceProcessor#processPropertySource(PropertySourceDescriptor descriptor)。
可以看到,在processPropertySource()方法中,會通過@PropertySource注解的屬性值解析出配置文件的內容,并且通過factory對象的createPropertySource()方法來創建PropertySource對象。
(5)解析DefaultPropertySourceFactory類的createPropertySource(String name, EncodedResource resource)方法。
源碼詳見:org.springframework.core.io.support.DefaultPropertySourceFactory#createPropertySource(String name, EncodedResource resource)。
createPropertySource()方法的源碼比較簡單,不再贅述。
(6)回到PropertySourceProcessor類的processPropertySource(PropertySourceDescriptor descriptor)方法。
在PropertySourceProcessor類的processPropertySource()方法中,創建完PropertySource對象后,會調用addPropertySource()方法將獲取到的屬性值添加到Spring的環境變量中。
(7)解析PropertySourceProcessor類的addPropertySource(PropertySource<?> propertySource)方法。
源碼詳見:org.springframework.core.io.support.PropertySourceProcessor#addPropertySource(PropertySource<?> propertySource)。
可以看到,在PropertySourceProcessor類的addPropertySource()方法中,會將解析出的配置文件的內容添加到Spring的環境變量中。具體就是在PropertySourceProcessor類的addPropertySource()方法中,獲取到ConfigurableEnvironment中的MutablePropertySources對象,用來存儲解析出的配置文件中的配置項內容。如果有相同的配置項內容,將existing對象強轉為CompositePropertySource類型,把新舊相同的配置項進行合并,再放到MutablePropertySources對象中。
后續就可以通過Spring的環境變量,來獲取到配置文件中的配置項內容。
至此,@PropertySource注解在Spring源碼中的執行流程分析完畢。
六、總結
@PropertySource注解講完了,我們一起總結下吧!
本章,首先介紹了@PropertySource注解的源碼和使用場景,隨后,簡單給出了一個@PropertySource注解的使用案例。接下來,詳細分析了@PropertySource注解的源碼時序圖和@PropertySource注解在Spring源碼層面的執行流程。
七、思考
既然學完了,就開始思考幾個問題吧?
關于@PropertySource注解,通常會有如下幾個經典面試題:
- @PropertySource注解的執行流程?
- @PropertySource注解是如何將配置文件加載到環境變量的?
- @PropertySource注解有哪些使用場景?
- Spring中為何會設計一個@PropertySource注解來加載配置文件?
- 從@PropertySource注解的設計中,你得到了哪些啟發?