【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原理图

  【Spring】@PropertySource配置的用法与实现原理(十一)

  重点是那个紫色虚线


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

上一篇:@PropertySource和@ConfigurationProperties区别


下一篇:ISA的三种客户端