【Spring】@PropertySource配置的用法與實現原理(十一)


一、@PropertySource功能

  • 加載指定的屬性文件(*.properties)到 Spring 的 Environment 中。可以配合 @Value 和@ConfigurationProperties 使用。

  • @PropertySource 和 @Value 組合使用,可以將自定義屬性文件中的屬性變量值注入到當前類的使用@Value注解的成員變量中。

  • @PropertySource 和 @ConfigurationProperties 組合使用,可以將屬性文件與一個Java類綁定,將屬性文件中的變量值注入到該Java類的成員變量中。

二、@PropertySource源碼

 1 package org.springframework.context.annotation;
 2 
 3 import java.lang.annotation.Documented;
 4 import java.lang.annotation.ElementType;
 5 import java.lang.annotation.Repeatable;
 6 import java.lang.annotation.Retention;
 7 import java.lang.annotation.RetentionPolicy;
 8 import java.lang.annotation.Target;
 9 
10 import org.springframework.core.io.support.PropertySourceFactory;
11 
12 @Target(ElementType.TYPE)
13 @Retention(RetentionPolicy.RUNTIME)
14 @Documented
15 @Repeatable(PropertySources.class)
16 public @interface PropertySource {
17 
18     /**
19      * 屬性源的名稱
20      */
21     String name() default "";
22 
23     /**
24      * 屬性文件的存放路徑
25      */
26     String[] value();
27 
28     /**
29      * 如果指定的屬性源不存在,是否要忽略這個錯誤
30      */
31     boolean ignoreResourceNotFound() default false;
32 
33     /**
34      * 屬性源的編碼格式
35      */
36     String encoding() default "";
37 
38     /**
39      * 屬性源工廠
40      */
41     Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
42 
43 }

三、使用示例

  屬性文件:person.properties

1 person.name=小白
2 person.age=18

1、@PropertySource + @Value

 1 @Component
 2 @PropertySource(value = {"person.properties"})
 3 public class Person {
 4 
 5     @Value("${person.name}")
 6     private String name;
 7 
 8     @Value("${person.age}")
 9     private int age;
10 
11 
12     @Override
13     public String toString() {
14         return "Person{" +
15                 "name='" + name + '\'' +
16                 ", age=" + age +'\'' +
17                 '}';
18     }
19 }

2、 @PropertySource 和 @ConfigurationProperties

  注意@ConfigurationProperties在SpringBoot中才有的注解

 1 import org.springframework.boot.context.properties.ConfigurationProperties;
 2 import org.springframework.context.annotation.PropertySource;
 3 import org.springframework.stereotype.Component;
 4 
 5 @Component
 6 @PropertySource(value = {"person.properties"})
 7 @ConfigurationProperties(prefix = "person")
 8 public class Person2 {
 9 
10     private String name;
11 
12     private int age;
13 
14 
15     @Override
16     public String toString() {
17         return "Person{" +
18                 "name='" + name + '\'' +
19                 ", age=" + age +'\'' +
20                 '}';
21     }
22 } 

四、實現原理

1、解析,當Spring容器初始化的時候,內置對象ConfigurationClassParser,會對配置類進行解析。

   ConfigurationClassParser#parse() -> processConfigurationClass() -> doProcessConfigurationClass() -> processPropertySource()

  processPropertySource() 解析Class上的@PropertySource注解

 1 // 處理每一個屬性源,最終加入到環境上下文里面去~
 2 private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
 3     // 獲取 propertySource 注解 name的值
 4     String name = propertySource.getString("name");
 5     if (!StringUtils.hasLength(name)) {
 6         name = null;
 7     }
 8     // 獲取 propertySource 注解 encoding 的編碼
 9     String encoding = propertySource.getString("encoding");
10     if (!StringUtils.hasLength(encoding)) {
11         encoding = null;
12     }
13     // 獲取 propertySource 注解 value 的值
14     String[] locations = propertySource.getStringArray("value");
15     Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
16     // 獲取 ignoreResourceNotFound 注解 ignoreResourceNotFound 的值 默認:false
17     boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
18 
19     // 獲取 ignoreResourceNotFound 注解 factory 的值 默認:PropertySourceFactory.class
20     Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
21     PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
22             DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
23 
24     // 遍歷位置路徑
25     for (String location : locations) {
26         try {
27             // 根據環境解析路徑
28             String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
29             // 處理好占位符后,獲取資源
30             Resource resource = this.resourceLoader.getResource(resolvedLocation);
31             // 添加屬性源
32             addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
33         } catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
34             // Placeholders not resolvable or resource not found when trying to open it
35             if (ignoreResourceNotFound) {
36                 if (logger.isInfoEnabled()) {
37                     logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
38                 }
39             } else {
40                 throw ex;
41             }
42         }
43     }
44 }

 2、添加到環境屬性源集

 1 private void addPropertySource(PropertySource<?> propertySource) {
 2     String name = propertySource.getName();
 3     // 從環境里把MutablePropertySources拿出來,准備向里面添加
 4     MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
 5     // 判斷解析器中的屬性源名單是否包含 此屬性源
 6 
 7     // 這里有個暖心的處理:若出現同名的配置文件,它會兩個都保存着,聯合形成一個CompositePropertySource  這樣它哥倆就都會生效了
 8     // 否則MutablePropertySources 的Map里面的name是不能同名的,我覺得這個做法還是很暖心的~~~
 9     if (this.propertySourceNames.contains(name)) {
10         // We've already added a version, we need to extend it
11         // 我們已經添加了一個版本,我們需要擴展它
12         PropertySource<?> existing = propertySources.get(name);
13         // 是否已經存在相同名的屬性源
14         if (existing != null) {
15             // 根據屬性源 是否是 資源屬性源 ,獲取一個新源
16             PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
17                     ((ResourcePropertySource) propertySource).withResourceName() : propertySource);
18             // 已經存在的屬性源,是否屬於 組合屬性源
19             if (existing instanceof CompositePropertySource) {
20                 // 屬於組合屬性源
21                 // 添加到集合第一位
22                 ((CompositePropertySource) existing).addFirstPropertySource(newSource);
23             } else {
24                 if (existing instanceof ResourcePropertySource) {
25                     existing = ((ResourcePropertySource) existing).withResourceName();
26                 }
27                 // 創建一個組合屬性源
28                 CompositePropertySource composite = new CompositePropertySource(name);
29                 // 后添加的反而在最上面的~~~ 已經存在會被擠下來一個位置
30                 composite.addPropertySource(newSource);
31                 composite.addPropertySource(existing);
32                 // 把已經存在的這個name替換成composite組合的
33                 propertySources.replace(name, composite);
34             }
35             return;
36         }
37     }
38     // 解析器屬性源名單為空
39     if (this.propertySourceNames.isEmpty()) {
40         // 添加到環境屬性源集中
41         propertySources.addLast(propertySource);
42     } else {
43         // 若你不是第一個,那就把你放在已經導入過的最后一個的前一個里面
44         // 獲取名單最后一個
45         String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
46         // 在最后一個前面插入
47         propertySources.addBefore(firstProcessed, propertySource);
48     }
49     // 添加到解析器屬性源名單中
50     this.propertySourceNames.add(name);
51 }

3、之后,在對象初始化完成,復制賦值時,

  populateBean() -> AutowiredAnnotationBeanPostProcessor對象后置處理 -> postProcessProperties()后置處理屬性

 1 @Override
 2 public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
 3     // 注入元信息,找到自動注入的元信息
 4     InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
 5     try {
 6         // 調用 metadata 注入方法
 7         metadata.inject(bean, beanName, pvs);
 8     }
 9     catch (BeanCreationException ex) {
10         throw ex;
11     }
12     catch (Throwable ex) {
13         throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
14     }
15     return pvs;
16 }

 

  metadata.inject() 方法中,通過解析@Value注解,拿到@Value注解的key值,拿到key之后,由上下文環境對象中,通過解析環境中存在的環境屬性源集,獲取到key所對應的值,然后使用反射的原理,給對象屬性進行賦值

四、@PropertySource原理圖

  

  重點是那個紫色虛線


參考:https://blog.csdn.net/qq_37312838/article/details/108237678


免責聲明!

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



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