Springboot中PropertySource注解多環境支持以及原理


摘要: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM