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 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3297 1
|
索引 Python
XPath解析之获取属性
XPath解析(三)
537 10
|
数据采集 API 开发工具
淘系商品详情数据解析(属性youhui券sku详情图等)API接口开发系列
在电商领域,特别是像淘宝(淘系)这样的平台,商品详情数据对于商家、开发者以及数据分析师来说至关重要。这些数据包括但不限于商品属性、优惠券信息、SKU(Stock Keeping Unit)详情、商品图片、售后保障等。然而,直接访问淘宝的内部API接口通常需要特定的权限和认证,这通常只对淘宝的合作伙伴或内部开发者开放。 不过,对于需要这些数据的第三方开发者或商家,有几种方式可以间接获取或解析淘系商品详情数据: ——在成长的路上,我们都是同行者。这篇关于商品详情API接口的文章,希望能帮助到您。期待与您继续分享更多API接口的知识,请记得关注Anzexi58哦!
|
JavaScript 前端开发 算法
【Vue秘籍揭秘】:掌握这一个技巧,让你的列表渲染速度飙升!——深度解析`key`属性如何成为性能优化的秘密武器
【8月更文挑战第20天】Vue.js是一款流行前端框架,通过简洁API和高效虚拟DOM更新机制简化响应式Web界面开发。其中,`key`属性在列表渲染中至关重要。本文从`key`基本概念出发,解析其实现原理及最佳实践。使用`key`帮助Vue更准确地识别列表变动,优化DOM更新过程,确保组件状态正确维护,提升应用性能。通过示例展示有无`key`的区别,强调合理使用`key`的重要性。
341 3
|
JSON 前端开发 API
【淘系】商品详情属性解析(属性规格详情图sku等json数据示例返回参考),淘系API接口系列
在淘宝(或天猫)平台上,商品详情属性(如属性规格、详情图、SKU等)是商家在发布商品时设置的,用于描述商品的详细信息和不同规格选项。这些信息对于消费者了解商品特性、进行购买决策至关重要。然而,直接通过前端页面获取这些信息的结构化数据(如JSON格式)并非直接暴露给普通用户或开发者,因为这涉及到平台的商业机密和数据安全。 不过,淘宝平台提供了丰富的API接口(如淘宝开放平台API),允许有资质的开发者或合作伙伴通过编程方式获取商品信息。这些API接口通常需要注册开发者账号、申请应用密钥(App Key)和秘钥(App Secret),并遵守淘宝的API使用协议。
|
数据采集 JSON API
淘系商品详情图属性sku价格解析,API接口系列
淘宝(Taobao)作为阿里巴巴集团旗下的电商平台,其商品详情图、属性、SKU和价格的采集通常不直接通过公开的API接口来实现,因为淘宝的API主要面向商家和开发者提供店铺管理、订单处理、物流查询等功能,并不直接提供商品详情页的完整数据抓取接口
|
XML API 数据库
商品详情数据API接口概念(sku详情图属性等全面的解析)
商品详情数据API接口是指一种编程接口(API, Application Programming Interface),它允许开发者或系统以编程方式获取商品的详细信息,包括但不限于SKU(Stock Keeping Unit,库存量单位)的详细信息、商品图片、商品属性、价格、库存状态、用户评价等。这种接口通常由电商平台、商品数据库服务商或第三方数据提供商提供,旨在帮助开发者或企业快速集成商品数据到其应用程序或系统中。
|
缓存 图形学 UED
U3D开发技术深度解析:异步场景加载与资源管理优化策略
【7月更文第11天】在Unity3D(简称U3D)游戏开发中,优化场景加载与资源管理是提升用户体验的关键一环。通过实现高效的异步场景加载和智能的资源管理策略,我们能显著缩短玩家的等待时间,提升游戏流畅度。本文将详细介绍这两种技术的应用,并提供实用的代码示例。
1655 0
|
Java 编译器
全面解析JVM加载中初始化的时机
全面解析JVM加载中初始化的时机

推荐镜像

更多
  • DNS