Spring中PropertySource属性源配置文件的优先级、顺序问题大解析(加载流程)【享学Spring】(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring中PropertySource属性源配置文件的优先级、顺序问题大解析(加载流程)【享学Spring】(下)

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

相关文章
|
2月前
|
XML Java 数据格式
Spring从入门到入土(xml配置文件的基础使用方式)
本文详细介绍了Spring框架中XML配置文件的使用方法,包括读取配置文件、创建带参数的构造对象、使用工厂方法和静态方法创建对象、对象生命周期管理以及单例和多例模式的测试。
123 7
Spring从入门到入土(xml配置文件的基础使用方式)
|
2天前
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
2月前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
1月前
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
54 8
|
1月前
|
JSON PHP 数据格式
PHP解析配置文件的常用方法
INI文件是最常见的配置文件格式之一。
54 12
|
2月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
178 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
2月前
|
存储 安全 网络协议
Elasticsearch 配置文件解析
【10月更文挑战第3天】Elasticsearch 配置文件解析
106 3
|
3月前
|
索引 Python
XPath解析之获取属性
XPath解析(三)
65 10
|
3月前
|
Rust Python
Python 解析 toml 配置文件
Python 解析 toml 配置文件
63 1
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2

推荐镜像

更多