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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 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

相关文章
|
25天前
|
负载均衡 算法 Java
Spring Cloud全解析:负载均衡算法
本文介绍了负载均衡的两种方式:集中式负载均衡和进程内负载均衡,以及常见的负载均衡算法,包括轮询、随机、源地址哈希、加权轮询、加权随机和最小连接数等方法,帮助读者更好地理解和应用负载均衡技术。
|
9天前
|
Java 对象存储 开发者
解析Spring Cloud与Netflix OSS:微服务架构中的左右手如何协同作战
Spring Cloud与Netflix OSS不仅是现代微服务架构中不可或缺的一部分,它们还通过不断的技术创新和社区贡献推动了整个行业的发展。无论是对于初创企业还是大型组织来说,掌握并合理运用这两套工具,都能极大地提升软件系统的灵活性、可扩展性以及整体性能。随着云计算和容器化技术的进一步普及,Spring Cloud与Netflix OSS将继续引领微服务技术的发展潮流。
22 0
|
1天前
|
程序员 C++
C++编程:While与For循环的流程控制全解析
总结而言,`while`循环和 `for`循环各有千秋,它们在C++编程中扮演着重要的角色。选择哪一种循环结构应根据具体的应用场景、循环逻辑的复杂性以及个人的编程风格偏好来决定。理解这些循环结构的内在机制和它们之间的差异,对于编写高效、易于维护的代码至关重要。
7 1
|
3天前
|
Rust Python
Python 解析 toml 配置文件
Python 解析 toml 配置文件
10 1
|
23天前
|
XML 监控 Java
Spring Cloud全解析:熔断之Hystrix简介
Hystrix 是由 Netflix 开源的延迟和容错库,用于提高分布式系统的弹性。它通过断路器模式、资源隔离、服务降级及限流等机制防止服务雪崩。Hystrix 基于命令模式,通过 `HystrixCommand` 封装对外部依赖的调用逻辑。断路器能在依赖服务故障时快速返回备选响应,避免长时间等待。此外,Hystrix 还提供了监控功能,能够实时监控运行指标和配置变化。依赖管理方面,可通过 `@EnableHystrix` 启用 Hystrix 支持,并配置全局或局部的降级策略。结合 Feign 可实现客户端的服务降级。
100 23
|
6天前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
13 1
|
8天前
|
监控 数据挖掘 BI
项目管理流程全解析及关键步骤介绍
项目管理流程是项目成功的基石,涵盖启动、规划、执行、监控和收尾等阶段。Zoho Projects 等软件可提高效率,支持结构化启动与规划、高效执行与协作及实时监控。这些流程和工具对项目的全局视角、团队协作和风险控制至关重要。项目管理软件适用于不同规模企业,实施时间因软件复杂度和企业准备而异。
23 2
|
3天前
|
Python
Python 解析 yaml 配置文件
Python 解析 yaml 配置文件
10 0
|
3天前
|
Python
Python 解析 ini 配置文件
Python 解析 ini 配置文件
13 0
手机上网流程解析
【9月更文挑战第5天】

推荐镜像

更多
下一篇
无影云桌面