MutablePropertySources
Mutable:可变的
它包含有多个数据源,并且提供对他们操作的方法~
public class MutablePropertySources implements PropertySources { // 持有多个PropertySource,并且它是个CopyOnWriteArrayList 放置了并发问题 private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); public MutablePropertySources() { } // 注意:此处是循环调用的addLast方法~~~~~~~~~~~ public MutablePropertySources(PropertySources propertySources) { this(); for (PropertySource<?> propertySource : propertySources) { addLast(propertySource); } } @Override public Iterator<PropertySource<?>> iterator() { return this.propertySourceList.iterator(); } @Override public Spliterator<PropertySource<?>> spliterator() { return Spliterators.spliterator(this.propertySourceList, 0); } // 复写了父类的Default方法~~~直接使用List的流~ @Override public Stream<PropertySource<?>> stream() { return this.propertySourceList.stream(); } // 此处注意:使用的是index,并且使用的是named静态方法~~~ 因为这里是根据name来查找 // 而上面我们说了,关于PropertySource的相等只和name有关而已~ @Override @Nullable public PropertySource<?> get(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); return (index != -1 ? this.propertySourceList.get(index) : null); } // 放在List的顶部=======注意:都先remove了,避免重复出现多个============ public void addFirst(PropertySource<?> propertySource) { removeIfPresent(propertySource); this.propertySourceList.add(0, propertySource); } public void addLast(PropertySource<?> propertySource) { removeIfPresent(propertySource); this.propertySourceList.add(propertySource); } // 把propertySource放在指定名字的relativePropertySourceName的前面 public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) { // 若relativePropertySourceName和propertySource同名,抛出异常~ assertLegalRelativeAddition(relativePropertySourceName, propertySource); removeIfPresent(propertySource); // 若relativePropertySourceName里不存在 这里也会抛出异常~ int index = assertPresentAndGetIndex(relativePropertySourceName); // 放在指定index的位置~ addAtIndex(index, propertySource); } public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) { ... } // 获取指定propertySource的优先权,实际就是index角标。 // 显然角标正数情况下越小越优先。0表示最优先,但是-1表示不存在~~~ public int precedenceOf(PropertySource<?> propertySource) { return this.propertySourceList.indexOf(propertySource); } // 根据名称来移除 @Nullable public PropertySource<?> remove(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); return (index != -1 ? this.propertySourceList.remove(index) : null); } public void replace(String name, PropertySource<?> propertySource) { int index = assertPresentAndGetIndex(name); this.propertySourceList.set(index, propertySource); } public int size() { return this.propertySourceList.size(); } @Override public String toString() { return this.propertySourceList.toString(); } ... }
MutablePropertySources它更像是一个管理器,管理着所有的PropertySource们。然后调用者最终调用getProperty()的时候,就会按照优先级从所有的PropertySource取值,具体逻辑上篇博文有详细讲述,参考PropertySourcesPropertyResolver#getProperty()方法
好了,有这些基础知识后,我们怎么运用和控制呢?也就是所谓的自定义属性配置文件,这在我们开发中使用得还是极多的(特别是SpringBoot~),下面以@PropertySource注解导入自定义属性源文件为例做个介绍
自定义属性源
@PropertySource属性源的加载流程
其实关于@PropertySource的加载,在之前有篇博文里已经有比较详细的分析了:
【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析
为了节约篇幅这里直接从ConfigurationClassParser开始:
class ConfigurationClassParser { ... @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { //1、解析嵌套内部类 //2、解析@PropertySource === 这是下面的内容 ==== // 相当于拿到所有的PropertySource注解,注意PropertySources属于重复注解的范畴~~~ for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { // 这个判断目前来说是个恒等式~~~ 所以的内置实现都是子接口ConfigurableEnvironment的实现类~~~~ // processPropertySource:这个方法只真正解析这个注解的地方~~~ if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } //3、解析@ComponentScan //4、解析@Import //5、解析@ImportResource //6、解析@Bean //7、解析接口default方法~~~ 也可以用@Bean标注 //8、解析super class父类 } // 处理每一个属性源,最终加入到环境上下文里面去~ private void processPropertySource(AnnotationAttributes propertySource) throws IOException { // 属性源的name,大多数情况下我们并不指定~ String name = propertySource.getString("name"); if (!StringUtils.hasLength(name)) { name = null; } String encoding = propertySource.getString("encoding"); if (!StringUtils.hasLength(encoding)) { encoding = null; } String[] locations = propertySource.getStringArray("value"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); // 此处注意:若我们都没有指定Factory的话,就会使用Spring默认的工厂,最终都是生成一个ResourcePropertySource(是个PropertiesPropertySource~~) // 所以它默认是只能处理Properties文件的(当然指定的格式的xml也是可以的),yaml是不能被支持的~~~~~~~~~~~ Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory"); PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass)); for (String location : locations) { try { String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); // 处理好占位符后,拿到这个资源~~~~ Resource resource = this.resourceLoader.getResource(resolvedLocation); // 重点就在这个方法里~~~把这个属性源添加进来~~~ addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } } } private void addPropertySource(PropertySource<?> propertySource) { String name = propertySource.getName(); // 从环境里把MutablePropertySources拿出来,准备向里面添加~~~~ MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); // 这里有个暖心的处理:若出现同名的配置文件,它会两个都保存着,联合形成一个CompositePropertySource 这样它哥俩就都会生效了 // 否则MutablePropertySources 的Map里面的name是不能同名的,我觉得这个做法还是很暖心的~~~ // 我觉得这个操作虽然小,但是足见Spring的小暖心~ if (this.propertySourceNames.contains(name)) { // We've already added a version, we need to extend it PropertySource<?> existing = propertySources.get(name); if (existing != null) { PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ? ((ResourcePropertySource) propertySource).withResourceName() : propertySource); if (existing instanceof CompositePropertySource) { ((CompositePropertySource) existing).addFirstPropertySource(newSource); } else { if (existing instanceof ResourcePropertySource) { existing = ((ResourcePropertySource) existing).withResourceName(); } CompositePropertySource composite = new CompositePropertySource(name); // 小细节:后添加的反而在最上面的~~~ 已经存在会被挤下来一个位置~ composite.addPropertySource(newSource); composite.addPropertySource(existing); // 把已经存在的这个name替换成composite组合的~~~ propertySources.replace(name, composite); } return; } } // 重要:手动导入进来的propertySource是放在最后面的(优先级最低) // 这段代码处理的意思是:若你是自己导入进来的第一个,那就放在最末尾 // 若你不是第一个,那就把你放在已经导入过的最后一个的前一个里面~~~ if (this.propertySourceNames.isEmpty()) { propertySources.addLast(propertySource); } else { String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1); propertySources.addBefore(firstProcessed, propertySource); } this.propertySourceNames.add(name); } }
从中可以看出一些小细节:
1、@PropertySource被解析的时机还是非常早的(次于内部类)
2、它允许同名的PropertySource存在,并且两个最终都会添加进来不会覆盖
3、通过注解@PropertySource导入进来的属性源的优先级是最低的~~~
4、location是支持占位符的,但是perperties文件里面其实也是支持占位符的(文件内的${xxx}这种占位符依旧可以用来引用本文件的内容、环境变量内容等等。它的解析实际是在给java属性赋值时~)
总结
我把这篇文章定位为SpringBoot自动化处理属性配置文件的先行文章,因为传统的Spring并不对自动处理,而都是我们手动去导入、添加属性配置文件~
但是我相信,在了解了这块内容后再去看SpringBoot的自动化方案实现,简直小巫见大巫,一种居高临下的赶脚。这又印证了我常说的那句话:你对Spring Framework有多了解决定了你对SpringBoot有多了解,你对SpringBoot有多了解决定了你对SpringCloud有多了解~
学无止境,keep moving