Spring Boot 配置优先级实战:application.yml、环境变量、命令行参数到底谁覆盖谁
Spring Boot 配置优先级实战:application.yml、环境变量、命令行参数到底谁覆盖谁线上出过这样的事:测试环境好好的,一上生产,数据库连的还是测试库。你在application.yml里明明写了生产地址,kubectl set env也注入了环境变量,可就是没生效。翻遍代码找不到 bug,最后发现是启动脚本里带了个--spring.datasource.url...命令行参数,把一切都盖掉了。Spring Boot 的配置来自十几个来源,它们有严格的优先级顺序。搞不清这套顺序,就会反复踩「我明明配了却不生效」的坑。这篇用可复现的例子把顺序讲透。一个最小复现:同一个 key,三处都配建一个属性app.mode,分别放到配置文件、环境变量、命令行,看最后谁赢。// AppController.javaRestControllerpublicclassAppController{Value(${app.mode})privateStringmode;GetMapping(/mode)publicStringmode(){returnapp.mode mode;// 打印最终生效的值}}# application.ymlapp:mode:from-yaml先只跑配置文件,访问/mode返回from-yaml。现在加环境变量再启动:# 环境变量里的点要换成下划线、大写(Relaxed Binding 规则)exportAPP_MODEfrom-envjava-jarapp.jar# 访问 /mode - app.mode from-env ← 环境变量赢了 yml再叠加命令行参数:exportAPP_MODEfrom-envjava-jarapp.jar--app.modefrom-cli# 访问 /mode - app.mode from-cli ← 命令行赢了环境变量结论初现:命令行 环境变量 application.yml。这三者是日常最容易撞车的,记牢它们的相对顺序能解决大部分问题。完整优先级顺序(高到低,记常用的几档)Spring Boot 官方定义了一长串 PropertySource,从高到低,高优先级覆盖低优先级。日常真正会用到的是这几档:命令行参数(--keyvalue)——最高,谁都盖不过它。SPRING_APPLICATION_JSON(环境变量里塞一段 JSON)。操作系统环境变量(APP_MODE...)。application-{profile}.yml(profile 专属配置,如application-prod.yml)。application.yml(通用配置)——最低的一档常用配置。jar 包内的默认值 /PropertySource。一句话:越「靠外、越临时」的来源优先级越高。命令行是启动那一刻拍板的,自然最高;打进 jar 的默认值最容易被各种外部值覆盖,最低。高频坑一:环境变量的名字怎么映射上面app.mode对应的环境变量是APP_MODE,不是app.mode。这是 Spring 的 **Relaxed Binding(宽松绑定)**规则:点.和中划线-都替换成下划线_全部大写举几个例子,别搞错:spring.datasource.url - SPRING_DATASOURCE_URL app.max-retry-count - APP_MAX_RETRY_COUNT server.servlet.context-path - SERVER_SERVLET_CONTEXT_PATH在 K8s / Docker 里注入配置,靠的就是这条规则。写错一个字母,环境变量就静默不生效,而且不报错——这是「配了没用」的头号原因。高频坑二:profile 专属配置和通用配置的关系很多人以为激活了prodprofile,application.yml就不读了。其实两者是叠加的:先加载application.yml,再用application-prod.yml覆盖同名 key,没被覆盖的 key 仍然用通用文件里的。# application.yml —— 通用,所有环境都读app:mode:defaulttimeout:3000# application-prod.yml —— 激活 prod 时叠加覆盖app:mode:productionjava-jarapp.jar--spring.profiles.activeprod# 最终:app.modeproduction(被 prod 覆盖),app.timeout3000(prod 没写,沿用通用)所以把「所有环境共用的默认值」放application.yml,把「各环境差异项」放application-{profile}.yml,是最省心的组织方式。高频坑三:Value 注入的是「解析后的最终值」有人担心用Value会不会绕过优先级、读到文件里的原始值。不会。Spring 把所有 PropertySource 合并成一个统一的Environment,Value和ConfigurationProperties拿到的都是优先级合并后的最终结果。想在代码里确认某个值到底从哪来,可以直接查Environment:ComponentpublicclassConfigDebugger{AutowiredprivateConfigurableEnvironmentenv;PostConstructpublicvoiddump(){// 打印最终生效的值System.out.println(final app.mode env.getProperty(app.mode));// 遍历所有 PropertySource,看它们的优先级顺序(靠前的优先级高)env.getPropertySources().forEach(ps-System.out.println(source: ps.getName()));}}启动时这段会按优先级从高到低打印所有配置源。开头那个「生产连了测试库」的事故,用这段代码一眼就能看到commandLineArgs排在最前面,凶手立现。实战:线上排查「配了不生效」的固定套路遇到配置不生效,别改代码瞎试,按这个顺序查:# 1. 确认启动命令有没有夹带命令行参数(最高优先级,最容易被忽略)ps-ef|grepjava# 看完整启动命令里有没有 --xxxyyy# 2. 确认环境变量名字对不对(Relaxed Binding)env|grep-iAPP# 看实际注入的环境变量# 3. 确认激活了哪个 profile# 日志里搜 The following profiles are active再配合上面的ConfigDebugger打印,基本 5 分钟内定位。小结核心顺序(高到低):命令行参数 环境变量 application-{profile}.ymlapplication.yml jar 内默认值,规律是「越外部越临时,优先级越高」。环境变量遵循Relaxed Binding:点和中划线换下划线、全大写,写错静默失效,是「配了没用」头号原因。profile 配置和通用配置是叠加覆盖,不是替换:共用值放application.yml,差异值放application-{profile}.yml。Value/ConfigurationProperties拿到的是合并后的最终值;排查用Environment.getPropertySources()看真实来源。记忆点:「配了不生效」先查启动命令有没有夹带--keyvalue——命令行参数优先级最高,谁都盖不过它。