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

简介: 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

相关文章
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
1362 0
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3274 1
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
418 0
|
JSON 小程序 UED
微信小程序 app.json 配置文件解析与应用
本文介绍了微信小程序中 `app.json` 配置文件的详细
2616 12
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——指定项目配置文件
在实际项目中,开发环境和生产环境的配置往往不同。为简化配置切换,可通过创建 `application-dev.yml` 和 `application-pro.yml` 分别管理开发与生产环境配置,如设置不同端口(8001/8002)。在 `application.yml` 中使用 `spring.profiles.active` 指定加载的配置文件,实现环境快速切换。本节还介绍了通过配置类读取参数的方法,适用于微服务场景,提升代码可维护性。课程源码可从 [Gitee](https://gitee.com/eson15/springboot_study) 下载。
785 0
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
在微服务架构中,随着业务复杂度增加,项目可能需要调用多个微服务。为避免使用`@Value`注解逐一引入配置的繁琐,可通过定义配置类(如`MicroServiceUrl`)并结合`@ConfigurationProperties`注解实现批量管理。此方法需在配置文件中设置微服务地址(如订单、用户、购物车服务),并通过`@Component`将配置类纳入Spring容器。最后,在Controller中通过`@Resource`注入配置类即可便捷使用,提升代码可维护性。
312 0
|
数据可视化 项目管理 UED
如何进行有效的优先级管理:6大模型解析
优先级管理看似简单,但要真正做到高效、精准,却需要方法和技巧的支撑。3分钟了解6种优先级管理方法。
1318 0
如何进行有效的优先级管理:6大模型解析
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
1685 8
|
JSON PHP 数据格式
PHP解析配置文件的常用方法
INI文件是最常见的配置文件格式之一。
300 12
|
索引 Python
XPath解析之获取属性
XPath解析(三)
520 10

热门文章

最新文章

推荐镜像

更多
  • DNS