摘要:Springboot中PropertySource注解的使用一文中,詳細講解了PropertySource注解的使用,通過PropertySource注解去加載指定的資源文件、然后將加載的屬性注入到指定的配置類,@value以及@ConfigurationProperties的使用。但是也遺留一個問題,PropertySource注解貌似是不支持多種環境的動態切換?這個問題該如何解決呢?我們需要從源碼中看看他到底是否支持。
首先,我們開始回顧一下上節課說的PropertySource注解的使用,實例代碼如下:
1 @PropertySource( name="jdbc-bainuo-dev.properties",
2 value={"classpath:config/jdbc-bainuo-dev.properties"},ignoreResourceNotFound=false,encoding="UTF-8")
我們使用了PropertySource注解中的參數有:name、value、ignoreResourceNotFound、encoding。其實PropertySource注解還有一個參數,那就是factory,該參數默認的值為PropertySourceFactory.class。
PropertySource注解的定義如下:
1 public @interface PropertySource {
2 String name() default "";
3 String[] value();
4 boolean ignoreResourceNotFound() default false;
5 String encoding() default "";
6 Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
7 }
我們不妨先看一下PropertySourceFactory接口是做什么的?該接口的核心定義代碼如下:
1 public interface PropertySourceFactory {
2 PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;
3 }
上述代碼中,PropertySourceFactory接口僅僅提供了一個createPropertySource方法,該方法就是創建PropertySource實例對象的,關於PropertySource的架構可以參考前面的系列文章進行學習,既然是接口,那肯定有實現類吧?該接口的默認實現類為DefaultPropertySourceFactory,代碼如下:
1 public class DefaultPropertySourceFactory implements PropertySourceFactory {
2 public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
3 return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
4 }
5 }
首先判斷name參數值是否為空,如果不為空直接實例化ResourcePropertySource並將name參數值進行傳遞,否則直接實例化ResourcePropertySource。看到這個地方的處理,感覺也沒什么神奇的地方,那么問題來了,我們思考如下三個問題:DefaultPropertySourceFactory 類什么時候被Spring框架調用呢?Name參數值是如何傳遞過來的呢?ResourcePropertySource實例化的時候做了什么呢?我們一個個的來看源碼進行分析。
1.1. DefaultPropertySourceFactory 類什么時候被Spring框架調用呢
第一個問題:DefaultPropertySourceFactory 類什么時候被Spring框架調用呢?這個我們就需要看一下我們定義的PropertySource注解是如何被Spring框架解析的?經過我的一系列排查,我找到了。Spring框架開始解析PropertySource注解的方法位於ConfigurationClassParser類中,為了防止大量的跟蹤源碼跟蹤丟失了,自己也糊塗了。我們直奔主題,看一下processPropertySource方法,如下所示:
1 private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
2 private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
3 String name = propertySource.getString("name");
4 if (!StringUtils.hasLength(name)) {
5 name = null;
6 }
7 String encoding = propertySource.getString("encoding");
8 if (!StringUtils.hasLength(encoding)) {
9 encoding = null;
10 }
11 String[] locations = propertySource.getStringArray("value");
12 boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
13 Class factoryClass = propertySource.getClass("factory");
14 PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
15 DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
16 for (String location : locations) {
17 try {
18 String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
19 Resource resource = this.resourceLoader.getResource(resolvedLocation);
20 addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
21 }
22 catch (IllegalArgumentException ex) {
23 if (ignoreResourceNotFound) {
24 if (logger.isInfoEnabled()) {
25 logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
26 }
27 }
28 else {
29 throw ex;
30 }
31 }
32 catch (IOException ex) {
33 }
34 }
上述代碼的邏輯分為如下幾個核心的步驟:
1. 開始解析name、encoding值。
2. 解析value(數組)以及ignoreResourceNotFound值。
3. 解析factory,如果該值沒有配置,默認為PropertySourceFactory則直接實例化DefaultPropertySourceFactory類,否則開始實例化自定義的類。換言之factory的處理類我們是可以進行自定義的。BeanUtils.instantiateClass是Spring中比較常用的一個工具類,其內部就是通過反射手段實例化類,在這里我們就不一一講解了。
4. 循環遍歷所有的location值,進行如下的處理。
4.1.對location進行SPEL表達式的解析。比如當前的配置環境中有一個屬性為app=shareniu,我們配置的location為${app}最終值為shareniu。通過這里的處理邏輯可以知道location支持多環境的切換以及表達式的配置。
4.2.使用資源加載器resourceLoader將resolvedLocation抽象為Resource。
4.3.調用addPropertySource屬性進行處理。將指定的資源處理之后,添加到當前springboot運行的環境中,這個前面的章節也詳細講解過類似的,在這里就不詳細說明了。注意:這里調用了DefaultPropertySourceFactory類中的createPropertySource方法了。
5.如果上述的任意步驟報錯,則開始查找ignoreResourceNotFound的值,如果該值為treu,則忽略異常,否則直接報錯。在這里我們可以看出ignoreResourceNotFound參數值的配置非常的重要。
1.2. ResourcePropertySource
我們看一下ResourcePropertySource類的構造函數,主要看一下沒有name參數的構造函數,如下所示:
1 public ResourcePropertySource(EncodedResource resource) throws IOException {
2 super(getNameForResource(resource.getResource()),PropertiesLoaderUtils.loadProperties(resource));
3 this.resourceName = null;
4 }
上述代碼,我們重點關注一下name值的生成邏輯。也就是getNameForResource中的處理邏輯,如下所示:
1 private static String getNameForResource(Resource resource) {
2 String name = resource.getDescription();
3 if (!StringUtils.hasText(name)) {
4 name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
5 }
6 return name;
7 }
通過resource中的getDescription方法獲取name值。如果name值為空,則重新生成,否則直接返回。大家又興起可以看看resource中各個子類的定義以及使用。
PropertiesLoaderUtils.loadProperties(resource)毋庸置疑就是加載指定的屬性文件了。
1.3. PropertySource多環境配置以及表達式使用
在springboot中,可以通過設置spring.profiles.active屬性,達到不同環境配置文件的動態切換。我們看一下這種方式如何使用,首先在application.properties增加如下的信息:
spring.profiles.active=dev
application.properties文件位置如下圖所示:
然后,我們修改CustomerDataSourceConfig1類,這個類的配置以及啟動類進行測試可以參考上一篇文章進行學習。
1 @PropertySource( name="jdbc-bainuo-dev.properties",value= {"classpath:config/jdbc-bainuo-$ {spring.profiles.active}.properties"},ignoreResourceNotFound=false,encoding="UTF-8")
2 public class CustomerDataSourceConfig1 {
3 }
這里注意value的值已經修改為了:"classpath:config/jdbc-bainuo-${spring.profiles.active}.properties"。
${spring.profiles.active}表達式可以動態的進行配置,從而達到動態切換不同的配置文件了。
————————————————
版權聲明:本文為CSDN博主「分享牛」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_30739519/article/details/78800490