深入理解 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 中,越靠后的配置优先级越高。

相关推荐
CodeSheep2 小时前
百度又一知名产品,倒下了!
前端·后端·程序员
li.wz2 小时前
溯源数据清洗:一次由“可控”到“失控”的复盘
java·后端·doris
仅此,2 小时前
Java请求进入Python FastAPI 后,请求体为空,参数不合法
java·spring boot·python·组合模式·fastapi
毕设源码-郭学长2 小时前
【开题答辩全过程】以 基于springboot的健身房信息管理为例,包含答辩的问题和答案
java·spring boot·后端
L Jiawen2 小时前
【Web】RESTful风格
前端·后端·restful
用户6802659051192 小时前
2026年企业级网络监控选型指南
javascript·后端·面试
Rysxt_2 小时前
Spring Boot 4.0 新特性深度解析与实战教程
java·spring boot·后端
程序员飞哥2 小时前
2025 年的寒冬,我这个大龄程序员失业了
后端·程序员
qq_256247052 小时前
Google Labs 新品实测:Mixboard、Flow 和 Learn Your Way 上手体验
后端