
1. 为什么要指定配置文件多环境下的那点实践1.1 先搞清楚 Spring Boot 的配置文件加载顺序用java -jar启动 Spring Boot 应用是部署阶段最常用的方式之一。但很多人在这个环节上栽过跟头本地明明用 IDEA 跑得好好的一到测试服务器或者生产服务器应用加载的配置就不是自己以为的那一份数据库地址还是指向本地日志级别也全乱了。要理解这个问题得先看看 Spring Boot 对配置文件的内置加载顺序。Spring Boot 启动时会按照固定的优先级查找application.properties或application.yml大致顺序从高到低是这样的命令行参数、SPRING_APPLICATION_JSON内嵌 JSON、Servlet 参数、JNDI、Java 系统属性、操作系统环境变量、application-{profile}.propertiesjar 包外的/config子目录、application-{profile}.propertiesjar 包外、application-{profile}.propertiesjar 包内、application.propertiesjar 包外/config……最后才是 jar 包内的application.properties。看到重点了吗外部的配置文件优先级永远高于 jar 包内部的。这意味着你可以把application.properties放到和 jar 同级的目录下不需要重新打包就能覆盖原来的配置。这也是运维侧最常用的一招。1.2 Profile 究竟帮我们解决了什么Spring Boot 的 Profile 机制本质上是给同一套配置做“分环境隔离”。比如application.yml里放公共配置application-dev.yml放开发环境连接信息application-prod.yml放生产环境连接信息。启动时通过spring.profiles.active告诉 Spring Boot你现在给我加载哪一份子配置。这样设计的好处很直接环境差异不用再靠注释切换了。很多人早期写项目习惯把 dev、test、prod 三个环境的配置全写在同一个application.yml里用注释块来切换一旦忘记注释回来就相当于把测试库暴露在生产环境上。Profile 机制从结构上就杜绝了这种低级风险。在java -jar启动场景下指定配置文件的核心手段无非下面几种命令行参数、环境变量、系统属性、外部配置文件覆盖。下面挨个说透包括用法和坑。2. 方式一命令行参数直接指定 Profile2.1 最简单的一行命令假设你打好了app.jar里面已经有application.yml、application-dev.yml、application-prod.yml。启动时想用生产配置一行命令就能搞定java -jar app.jar --spring.profiles.activeprod命令行参数是所有配置来源里优先级最高的高于环境变量也高于 jar 包里的application.properties中的默认值。这一点非常关键——就算你在application.yml里写了spring.profiles.active: dev只要命令行带上了--spring.profiles.activeprod最终生效的还是 prod。注意--spring.profiles.activeprod这个写法本质上是 Spring Boot 对命令行参数的一种“宽松绑定”。它等价于你设置了spring.profiles.active这个属性。所以多个参数也可以叠加java -jar app.jar --spring.profiles.activeprod --server.port8081。命令行里写的--开头参数会被 Spring Boot 自动收集到SpringApplication的默认属性源里。如果你用的是application.properties而不是 yml原理完全相同。Spring Boot 对这两者的处理机制是一致的只是 yml 的层级结构更清晰现在新项目里用的更多。2.2 同时激活多个 ProfileProfile 不是只能激活一个。比如你既想用prod环境配置又想临时打开swagger这个调试 profile写法是逗号分隔java -jar app.jar --spring.profiles.activeprod,swagger这种情况下Spring Boot 会同时加载application-prod.yml和application-swagger.yml。需要注意的是如果两个 profile 文件里配置了同一个属性后加载的会覆盖前面的吗答案是不会简单粗暴地覆盖Spring Boot 对多个 profile 的加载顺序有内部机制相同属性在不同 profile 文件里出现时以最后的为准但这个“最后的”取决于 profile 文件的激活顺序而不是你在命令行里的书写顺序。为了避免这种隐性行为我的建议是同一属性不要分散在多个 profile 文件里重复定义。公共的放application.yml各环境有差异的放各自 profile 文件别交叉。另外在实际生产环境里我见过一种组合用法--spring.profiles.activeprod再加上--spring.profiles.includecommon-tls。include和active的区别在于include指定的 profile 也会被加载但它不受“默认 profile”逻辑干扰而且它适合放那些“无论哪个环境都得带上”的公共子模块。不过说实话这种用法在java -jar启动场景下用得不多大多数团队还是把公共配置放在主application.yml里。3. 方式二环境变量与系统属性3.1 SPRING_PROFILES_ACTIVE 环境变量这是运维同学最喜欢的方式也是容器部署比如 Docker 启动命令里-e参数和 systemd 服务里最常用的方式。因为不需要修改启动命令本身只要在运行环境里设置一个环境变量即可export SPRING_PROFILES_ACTIVEprod java -jar app.jarSpring Boot 的Environment抽象会把环境变量中的SPRING_PROFILES_ACTIVE自动映射为spring.profiles.active。这是它内置的“宽松绑定”规则环境变量名的大写下划线风格会映射成点分属性。在 Docker 运行场景里尤其方便docker run -e SPRING_PROFILES_ACTIVEprod -p 8080:8080 myapp:latest这样镜像本身是通用的不需要为每个环境单独打一个镜像。同一个 jar 包测试环境跑时设置SPRING_PROFILES_ACTIVEdev生产环境设置SPRING_PROFILES_ACTIVEprod镜像完全一致这才是环境与构建分离的正确姿势。3.2 -Dspring.profiles.active 系统属性要注意优先级Java 系统属性也可以用来指定 profilejava -Dspring.profiles.activeprod -jar app.jar注意这里的-D必须写在-jar前面如果你写成java -jar app.jar -Dspring.profiles.activeprod那这个参数会被当成应用参数传给 Spring Boot而不是 Java 虚拟机参数spring.profiles.active根本不会被识别。优先级上命令行参数--spring.profiles.activeprod高于系统属性。所以当别人用了一个启动脚本里面系统属性写的是 dev你又在命令行追加了--spring.profiles.activeprod最终生效的是命令行里的 prod。我当时排查过类似问题脚本维护者非常困惑明明设置了SPRING_PROFILES_ACTIVEprod为什么日志显示的 profile 是 dev后来发现是启动脚本里有另一行-Dspring.profiles.activedev在作怪系统属性优先级高于环境变量所以环境变量被压住了。如果希望避免不同来源的属性互相“打架”可以在应用启动时打印当前生效的 profileSpringBootApplication public class App { public static void main(String[] args) { SpringApplication app new SpringApplication(App.class); app.run(args); } }然后在启动日志里加一个ApplicationRunner输出environment.getActiveProfiles()或者干脆在application.yml里加上spring.main.banner-mode: log配合自定义 banner。不过最直接的办法还是看 Spring Boot 启动日志里的这一行The following 1 profile is active: prod这一行基本能解决 90% 的“我的 profile 到底生效了没有”的疑问。4. 方式三直接指定配置文件路径4.1 --spring.config.location 的完整用法有时候不只是想切换 profile而是想把整个配置文件都换成外部自定义路径的。比如你不想把application.yml打进 jar 包而是希望启动时直接指定位于/etc/myapp/目录下的配置文件java -jar app.jar --spring.config.location/etc/myapp/application.yml这种方式会让 Spring Boot完全忽略jar 包内部的application.yml直接以你指定的文件作为配置文件来源。给定的路径可以是一个具体文件也可以是一个目录目录后面要有/java -jar app.jar --spring.config.location/etc/myapp/使用目录时Spring Boot 会在这个目录里找application.properties或application.yml。这种方式非常适合那种“配置不允许跟着镜像走”的敏感场景。需要特别提醒的是--spring.config.location的优先级高于 jar 包内的application.yml如果你指定了一个外部文件那么 jar 包内默认的配置就不再生效。这既是优点也是坑。优点是干净、不受内置配置干扰缺点是一旦外部文件没有配置某一个属性而它原来在 jar 包内默认配置里有那这个属性就变成了 null。所以用location时外部文件最好是一个完整的配置文件该有的都得有。4.2 --spring.config.additional-location 用法与语义和location不同additional-location是“附加”外部配置不是“替代”。它会在默认加载顺序的基础上把额外路径添加进去java -jar app.jar --spring.config.additional-location/etc/myapp/这样 jar 包内的application.yml仍然会加载但外部路径里的配置文件优先级更高对外部配置中出现的属性以外部为准外部没有的属性仍然用 jar 包内的默认值。这个方式我在处理“既要保留内置兜底配置又要让运维覆盖关键项”的场合下很常用。要注意一个细节additional-location指定的目录会在默认的classpath:/之后被追加因此是“后加入的搜索路径优先级更高”的机制。如果你同时指定了多个additional-location前面的路径优先级高于后面的。这个相对顺序在实际排查问题的时候很关键比如java -jar app.jar --spring.config.additional-location/etc/myapp/,/srv/backup/那么/etc/myapp/下的配置会覆盖/srv/backup/下的同名属性。5. 代码与构建层面的实践方式5.1 在启动类里指定 Profile你可以在SpringApplication构建时通过代码直接指定 profilepublic static void main(String[] args) { SpringApplication app new SpringApplication(App.class); app.setAdditionalProfiles(prod); app.run(args); }这样做的问题是配置被硬编码进了代码里后续想切换环境就要改代码重打包完全违背了“环境与构建分离”的原则。我一般不建议这么干只在一些极端场景下会考虑比如一个应用只跑在一个环境且没有运维去维护环境变量那在代码里写死也算一种“省事”的妥协。但凡有变更环境的可能就别写死在代码里。5.2 Maven Profile 配合 spring-boot-maven-plugin还有一种做法是通过 Maven 的 profile 在打包阶段就把配置替换好。比如在pom.xml里定义profiles profile idprod/id properties activatedProfileprod/activatedProfile /properties /profile /profiles然后在application.yml里写占位符spring: profiles: active: activatedProfile打包时指定mvn clean package -PprodMaven 的资源过滤会把activatedProfile替换成prod这样最终打进 jar 包里的application.yml默认就是spring.profiles.activeprod。你可以直接用java -jar app.jar启动不需要额外指定任何参数。这个方案的坑在于过滤功能记得开启。如果pom.xml里没有配置resources节点没有把src/main/resources的过滤打开那么activatedProfile不会被替换启动时 Spring Boot 会拿到一个带占位符的字符串然后报错说找不到对应的 profile。我当时遇到过一次排查了半天后来发现就是少了一段build resources resource directorysrc/main/resources/directory filteringtrue/filtering /resource /resources /build加上以后就正常了。这种方法的好处是启动命令干净适合交付给不太懂技术的部署人员坏处是每个环境都要单独打包一次镜像 file 也会不同和容器化“一次构建到处运行”的初衷有点冲突。6. 实操实录一个完整的启动与验证过程6.1 准备一套多环境配置假设项目结构如下app.jar application.yml application-dev.yml application-prod.yml主配置application.yml内容spring: application: name: my-demo-app server: port: 8080 logging: level: root: infoapplication-dev.ymlserver: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/dev_db logging: level: root: debugapplication-prod.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://prod-host:3306/prod_db这里我没有把数据库密码写进配置生产环境的敏感信息应该通过环境变量注入而不是直接写在application-prod.yml里随包发布。比如用户名密码用${DB_USERNAME}占位符运行时再从环境变量读取。6.2 启动命令与日志验证现在分别用几种方式启动并观察日志输出。方式一命令行参数java -jar app.jar --spring.profiles.activeprod启动日志中会出现类似The following 1 profile is active: prod看到这一行就说明 profile 生效了。方式二环境变量export SPRING_PROFILES_ACTIVEprod java -jar app.jar方式三外部配置文件java -jar app.jar --spring.config.additional-location/etc/myapp/日志中会显示加载的配置来源Loaded config file file:/etc/myapp/application.yml如果配置加载不正确优先看这一部分日志。Spring Boot 会把所有加载的配置文件来源都打印出来逐条排查很快。6.3 如何快速验证当前加载了哪份配置有时候配置多、来源杂光看 profile 还不够因为同一个属性可能在多个来源里被定义。我的做法是在启动类上加一个ApplicationRunnerComponent public class ConfigCheckRunner implements ApplicationRunner { private final Environment environment; public ConfigCheckRunner(Environment environment) { this.environment environment; } Override public void run(ApplicationArguments args) { System.out.println(Active profiles: String.join(,, environment.getActiveProfiles())); System.out.println(server.port environment.getProperty(server.port)); System.out.println(datasource.url environment.getProperty(spring.datasource.url)); } }这段代码会在应用启动完成后打印当前生效的关键配置。虽然不是多高级的操作但排查配置问题时非常实用也方便交接给运维同学做启动自检。7. 那些年踩过的坑常见问题速查与排查思路7.1 为什么指定了 profile 却没生效这个问题我被人问过很多次。最常见的三种原因参数写错了位置。-Dspring.profiles.activeprod写在了-jar app.jar后面被当成应用参数处理了。参数之间互相覆盖。系统属性和环境变量同时设置了不同的 profile系统属性优先级更高环境变量被忽略。可以用env | grep -i profile检查当前环境变量。application.yml里写了spring.profiles.active指向某个值你又用命令行参数切换了另一个值。命令行参数优先级最高按理说不应该出问题——但如果你在启动脚本里用了单引号把整个--spring.profiles.activeprod包起来而 shell 变量没有正确展开也可能会出现诡异行为。一个一个排查最快的方法是先打印完整启动命令确认参数展开后的真实样子。7.2 “没有 active profile 生效”怎么办如果日志里没有The following profile is active这一行说明 Spring Boot 没有检测到任何显式激活的 profile。这种情况下默认加载的只有application.yml。有一种偏好希望在没有任何 profile 激活时默认走 dev可以这样设置spring: profiles: active: dev但我要提醒一下如果你把active设置为 dev又在命令行传入--spring.profiles.activeprod生效的是 prod因为命令行优先级更高。这个逻辑本身没问题。但如果你在 jar 包内部的application.yml里写了active: dev而服务器上没设置任何参数后果就是服务器加载了 dev 配置。所以我的建议是默认值不要写死。用spring.profiles.default或干脆不设默认值强制部署时显式指定 profile这样反而不会因为忘记传参而把开发配置带到生产环境。7.3 外部配置文件优先级搞反了我再强调一遍外部路径比如/config子目录、--spring.config.location、--spring.config.additional-location指定的路径优先级高于 jar 包内部。这不是你主观能“调换”的是 Spring Boot 的硬规则。我见过一个场景运维把一份配置放在 jar 包同级目录下然后在application.yml里写了另一个数据源 URL结果数据库连接总是用外部的地址。运维觉得“jar 包里的配置应该覆盖外部的”追了半天最后发现是理解反了。记住一句话就好越靠外、越靠后加载的配置优先级越高。7.4 配置文件名带下划线还是中划线Spring Boot 对配置文件的文件名约定是application-{profile}.propertiesprofile 名称本身可以用中划线也可以用下划线但要注意宽松绑定的规则。比如 profile 名称my-prod在环境变量里对应的是SPRING_PROFILES_ACTIVEmy-prod不要写成MY_PROD。曾经有人把环境变量写成SPRING_PROFILES_ACTIVEMY_PROD而文件名是application-my-prod.yml结果死活加载不上。其实 Spring Boot 的宽松绑定在一定程度上能兼容大小写但保险起见profile 名字尽量用小写。7.5 启动慢或者加载顺序异常时的排查思路如果怀疑配置文件加载了但被后面的配置覆盖了可以用actuator的/actuator/env接口查看所有属性源以及每个属性的来源curl http://localhost:8080/actuator/env如果没引入 actuator可以先在 application 里加依赖重启一次再真实环境里去查。这不是生产环境长期依赖的手段但排查配置问题时临时开一下效果很好。另外Spring Boot 2.4 之后配置加载逻辑有一些调整spring.config.use-legacy-processing可以开启旧版行为。如果你在升级 Spring Boot 版本后发现配置风格不太一样了有可能是这个开关的影响。不过现在主流版本都基于新逻辑没必要为了兼容开旧模式。8. 我对配置文件组织的一些个人经验最后分享一点偏“工程管理”的心得。java -jar指定配置文件这件事技术本身不复杂真正复杂的是团队怎么约定配置文件的使用规范。我见过太多团队同一个应用在不同环境里的配置来源五花八门测试环境用环境变量预发环境用外部文件生产环境又用命令行参数。一旦出问题排查成本直线上升。我的建议是选两到三种固定的组合套路容器环境优先用SPRING_PROFILES_ACTIVE环境变量 外部挂载配置目录--spring.config.additional-location。传统虚拟机部署优先用命令行参数--spring.profiles.activexxx配合/etc/myapp/下的外部配置文件覆盖敏感项。测试本地联调优先用application-dev.yml加上 IDEA 的Active profiles启动选项开发环境不折腾。这样约定下来每个人看到启动命令就能猜到生产环境的 profile 是什么排查效率会高很多。配置文件的敏感信息处理也要重视。数据库密码、密钥这一类内容不要明文提交到 Git 仓库也不要打包进 jar。启动时通过环境变量占位符注入是当前主流安全实践里最基础的一环。哪怕只是个人项目也建议养成这个习惯。从我多年实际部署和排查的经验看Spring Boot 的配置体系是高度灵活的但灵活性也意味着更容易出错。指定配置文件的方式看似只是一条命令或一个环境变量的事背后却牵扯到优先级、加载顺序、打包方式等一连串逻辑。把这套逻辑彻底吃透了将来不管在哪一种部署环境里遇到配置问题都不会慌。如果你正被某个配置文件加载问题困扰建议先打开启动日志找到那几行Loaded config file和The following profile is active顺着它们追这个方向基本错不了。