一、@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