深入理解 PropertySource 与优先级:Spring Boot/Spring Cloud 配置体系的底层原理

文章目录

在 Spring Boot / Spring Cloud 项目里,我们每天都在和各种配置打交道:

  • application.yml / application.yaml / application.properties
  • Nacos 配置中心
  • 命令行参数
  • 系统环境变量
  • JVM 启动参数
  • 甚至历史上的 bootstrap.yml

但真正遇到问题时,很多人都会迷糊:

  • 为什么我在 Nacos 改了值,却被本地文件"覆盖"了?
  • 为什么 application.properties 的优先级看起来比 application.yml 高?
  • 多个 Nacos 配置(共享的、服务私有的)谁覆盖谁?
  • spring.config.import 里顺序一换,结果就跟着变?

这一切的根源,其实都指向同一个核心机制:

Spring 的 PropertySource 体系

只要搞懂 PropertySource 怎么存、怎么排、怎么查,配置优先级问题基本一网打尽。


一、所有配置最终都进了同一个"桶":Environment + PropertySource

Spring 有一个统一的配置入口:Environment。

无论你用的是:

  • 本地 application*.yml / .yaml / .properties
  • Nacos 配置中心
  • 命令行 --server.port=8081
  • 系统环境变量 SERVER_PORT=8082
  • JVM 参数 -Dserver.port=8083

最终都会被封装成一个个 PropertySource,存放在:

java 复制代码
environment.getPropertySources()

这个 PropertySources 实际上就是一个有序列表,顺序非常关键。


二、PropertySource 是什么?

简单理解:

一个 PropertySource 就代表一份配置来源。

常见类型有:

类型 说明
ResourcePropertySource 从 application.yml / properties 加载
MapPropertySource 普通 Map 构建的配置
SystemEnvironmentPropertySource 操作系统环境变量
PropertiesPropertySource JVM 系统属性(-Dkey=value)
CommandLinePropertySource 命令行参数(--key=value)
CompositePropertySource 多个配置源组合(如某些外部配置中心)

Spring Boot / Spring Cloud 启动时,会不断往 Environment 里"塞"各种 PropertySource,塞进去的顺序 + 插入的位置,决定了最终的优先级。


三、优先级的底层规则:谁在前,谁优先

核心规则只有一条:

PropertySources 列表中,越靠前的 PropertySource 优先级越高。

Spring 在解析属性值时,大致是这样:

java 复制代码
for (PropertySource<?> ps : environment.getPropertySources()) {
    if (ps.containsProperty("some.key")) {
        return ps.getProperty("some.key");
    }
}

也就是说:

  1. 从头开始遍历
  2. 第一个有这个 key 的 PropertySource 胜出
  3. 后面即便有同名 key,也完全没机会了

所以,"谁最终生效" = "谁在列表里排得更靠前"。


四、用真实打印结果,看清你项目里的最终优先级

假设项目中打印 PropertySources:

java 复制代码
@Autowired
private ConfigurableEnvironment environment;

@PostConstruct
public void printSources() {
    int i = 0;
    for (PropertySource<?> ps : environment.getPropertySources()) {
        System.out.println((i++) + " -> " + ps.getName());
    }
}

你实际拿到的输出类似这样:

text 复制代码
0 -> configurationProperties
1 -> CamundaBpmVersion
2 -> servletConfigInitParams
3 -> servletContextInitParams
4 -> systemProperties
5 -> systemEnvironment
6 -> random
7 -> cachedrandom
8 -> springCloudClientHostInfo

9  -> Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'
10 -> DEFAULT_GROUP@sky-dev.yml
11 -> DEFAULT_GROUP@application-dev.yml
12 -> Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'
13 -> Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/'

只要看这份顺序,你项目里所有配置的优先级关系就一目了然了。

结合这份结果,可以得出如下结论:

  1. 本地文件优先级:
text 复制代码
application.properties > application.yml > application.yaml

因为:

  • application.properties 在 index 9
  • application.yml 在 index 12
  • application.yaml 在 index 13

越靠前,优先级越高。

  1. Nacos 配置优先级:(import要看顺序,谁后写谁优先级高)
text 复制代码
sky-dev.yml > application-dev.yml

因为:

  • DEFAULT_GROUP@sky-dev.yml 在 index 10
  • DEFAULT_GROUP@application-dev.yml 在 index 11

也就是:服务私有配置 > 共享配置。

  1. Nacos vs 本地文件:

从顺序可见:

  • application.properties 在 Nacos 之前
  • sky-dev.yml、application-dev.yml 在本地 application.yml / yaml 之前

所以整体顺序是:

text 复制代码
(高)命令行 / JVM / 环境变量
    > application.properties
    > Nacos sky-dev.yml(服务私有)
    > Nacos application-dev.yml(共享)
    > application.yml
    > application.yaml(低)

这就是你当前工程里,真实的最终优先级。


五、本地文件之间的优先级:properties / yml / yaml

从实际打印顺序可以看到,Spring Boot 对本地配置文件的加载顺序类似:

  1. application.properties
  2. application.yml
  3. application.yaml

所以当同一个 key 同时存在于这三者中时,覆盖关系是:

text 复制代码
application.properties > application.yml > application.yaml

也就是说,如果你写了:

  • application.yaml: user.name.test=yaml
  • application.yml: user.name.test=yml
  • application.properties: user.name.test=props

最终 Environment.getProperty("user.name") 返回的是:

text 复制代码
props

六、Nacos 配置的优先级:和 spring.config.import 强相关

在 Spring Boot 3 / Spring Cloud 2022 / Spring Cloud Alibaba 2023 这一套组合下,Nacos 不再通过 bootstrap.yml 加载,而是统一使用:

yaml 复制代码
spring:
  config:
    import:
      - nacos:xxx
      - nacos:yyy

这里有一个非常关键的规则:

在同一个配置文件里,spring.config.import 中的多个位置:
谁写在后面,谁的优先级高(后者可以覆盖前者的同名 key)。

例如:

yaml 复制代码
spring:
  config:
    import:
      - nacos:application-dev.yml
      - nacos:sky-dev.yml
  • application-dev.yml 先导入
  • sky-dev.yml 后导入,优先级更高

冲突时:

text 复制代码
sky-dev.yml > application-dev.yml

反过来写:

yaml 复制代码
spring:
  config:
    import:
      - nacos:sky-dev.yml
      - nacos:application-dev.yml

则变成:

text 复制代码
application-dev.yml > sky-dev.yml

结论:在同一个配置文件中,spring.config.import 的"后写的"优先级更高。

你打印出来的 PropertySources 顺序,也刚好验证了这一点:
sky-dev.yml 排在 application-dev.yml 前面,是因为在当前版本的实现中,它被处理后挂载到了更高优先级的位置 ------ 最终还是要以实际打印结果为准。


七、为什么现在看不到 bootstrap.yml 了?

历史上(Spring Cloud 2.x):

  • Nacos / Config Server 等配置通常写在 bootstrap.yml
  • 因为 bootstrap context 比 application context 更早启动
  • 会在 PropertySources 中看到类似 bootstrapProperties 之类的条目

但在 Spring Boot 2.4+ / Spring Cloud 2021+ 之后:

  • bootstrap phase 被默认移除
  • 官方推荐使用新的 ConfigData 机制(spring.config.import)
  • Spring Cloud Alibaba 2023 更是完全放弃了 bootstrap.yml 模式

因此在现在这套版本组合下:

即使你写了 bootstrap.yml,默认也不会被加载,更不会出现在 PropertySources 里。


八、实战建议:如何自己分析"谁覆盖谁"?

  1. 启动时打印所有 PropertySource:

    java 复制代码
    @Autowired
    private ConfigurableEnvironment environment;
    
    @PostConstruct
    public void printSources() {
        int i = 0;
        for (PropertySource<?> ps : environment.getPropertySources()) {
            System.out.println((i++) + " -> " + ps.getName());
        }
    }
  2. 挑一个关键 key,定位它来自哪个 PropertySource:

    java 复制代码
    @PostConstruct
    public void debugKey() {
        String key = "user.name";
        for (PropertySource<?> ps : environment.getPropertySources()) {
            Object val = ps.getProperty(key);
            if (val != null) {
                System.out.println("key = " + key +
                                   ", value = " + val +
                                   ", from = " + ps.getName());
                break;
            }
        }
    }

    这样你能非常直观地知道:

    • 某个配置到底是从哪个文件 / 哪个 Nacos 配置拿到的
    • 为什么你的修改有没有生效

九、总结:

一句话记住 PropertySource 的核心逻辑

Spring Boot / Spring Cloud 所有配置优先级,最终都由 Environment.getPropertySources() 的顺序决定:
列表越靠前,优先级越高;在同一个 spring.config.import 中,越靠后的配置优先级越高。

相关推荐
2301_32241428043 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
金銀銅鐵5 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路6 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x6 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn8 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈9 小时前
AI 读你的提示词,其实很没耐心
后端
用户8356290780519 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123459 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒9 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记10 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端