
1. 先搞明白Spring Profiles 到底帮你解决了什么问题接触过实际项目的朋友应该都有感触配置文件才是项目里最容易出幺蛾子的地方。开发环境连本地数据库测试环境连测试库生产环境要连主库一个项目少则两套配置多则五六套。以前没有 Profiles 的时候大家就硬改 application.properties改完上线忘了改回来结果把测试环境的数据写进了生产库这种事我见过不止一次。Spring Profiles 的本质就是给不同的运行环境打标签。你可以在 Spring Boot 的 application.yml 或 application.properties 中为不同环境定义不同的配置片段然后通过激活某个或某几个 Profile告诉 Spring 当前环境我只要这些配置。它解决的痛点是三个一是环境隔离二是配置复用三是行为开关。但另一个常见问题是随着微服务拆分、基础组件增多一个环境对应一套配置这个模型慢慢不够用了。有时候你的数据库配置和生产环境绑定但消息队列配置却和公共模块绑定有时候你有一个公共的监控配置不管哪个环境都要加载有时候你又希望某个 Profile 在激活另一个 Profile 时被顺带激活。这时候光靠 spring.profiles.active 就捉襟见肘了于是就有了 spring.profiles.include。文章里我不会讲太深奥的源码重点就是这两个配置项到底怎么用、区别在哪、优先级顺序怎么排、实际项目里怎么搭配。2. spring.profiles.active 和 spring.profiles.include 的本质区别2.1 一句话先记下两者定位spring.profiles.active 是从外部指定当前环境它代表的是入口和主导。spring.profiles.include 是在当前配置文件中主动声明我还需要哪些附加 Profile它代表的是附加和组合。这两者最直观的现象是spring.profiles.active 可以通过启动参数、环境变量、JVM 系统属性、命令行参数等方式外部覆盖而 spring.profiles.include 基本只能在配置文件里写死。用一个生活化的类比来说spring.profiles.active 很像你出门前决定今天我是上班族身份这个身份决定了你基本的行为模式而 spring.profiles.include 很像你在这个身份基础上还要叠加今天我是健身房会员、今天我是要去银行办事的人这些附加身份。主身份由外部情境决定附加身份由你自己补充。2.2 active 和 include 在配置加载顺序上的差异这是很多人在踩的坑。Spring Boot 在加载配置时并不是简单地先加载 active 指定的文件再加载 include 指定的文件顺序是反过来的。实际加载顺序是先加载 spring.profiles.include 引入的 Profile 配置再加载 spring.profiles.active 指定的 Profile 配置。也就是说相同配置项下spring.profiles.active 的优先级高于 spring.profiles.include。Spring Boot 官方文档有一句很关键的说明spring.profiles.include 中的 Profile 会在 active 之前被处理。这意味着如果同一属性在 active Profile 和 include Profile 中都定义了active 的值会覆盖 include 的值。打个比方include 好比是基础包active 好比是个性定制定制的内容永远覆盖基础包的内容。我在实际项目中就遇到过这样的问题公共监控配置放在 include 的 common Profile 里但为了调试临时把日志级别调成 DEBUG 写在 active 的 dev Profile 里结果发现日志级别没生效后来排查才知道是配置项被 include 里的配置覆盖了。当时就是没搞清楚这个加载顺序。2.3 active 的多种外部注入方式优先级从高到低spring.profiles.active 最大的价值是支持外部注入。Spring Boot 的配置优先级本身就遵循一套非常复杂的规则从命令行参数到 JVM 系统属性、再到环境变量、再到 application-{profile}.properties 配置文件逐层覆盖。就 spring.profiles.active 而言常见的注入方式有命令行参数--spring.profiles.activeprod这是优先级最高的方式适合部署脚本传入。JVM 系统属性-Dspring.profiles.activeprod适合启动脚本设置。环境变量SPRING_PROFILES_ACTIVEprod适合容器化部署时在 Docker/K8s 中注入。配置文件里的spring.profiles.activeprod适合本地开发或默认值场景。命令行参数最优先其次是 JVM 系统属性再次是环境变量最后是配置文件里的值。这个优先级顺序在官方文档中描述得很清楚。投影到实际场景本地开发时你在 application.properties 里写死 spring.profiles.activedev到了部署环节运维的启动脚本传了 --spring.profiles.activeprod那么这个命令行参数会覆盖配置文件里的值。如果你在配置文件中没有写 active但环境变量里有 SPRING_PROFILES_ACTIVEprod则会使用环境变量的值。2.4 include 为什么不能外部覆盖其实是设计如此很多人问过include 能不能像 active 一样用启动参数指定答案是不能。spring.profiles.include 是配置文件的内部声明Spring Boot 在设计时就没打算让它被外部覆盖。为什么从职责来看include 天然适合表达当前配置模块组合... 如果 include 可以被外部覆盖那配置组合就完全由运维说了算开发预期的基础模块组合就形同虚设了。这违背了 include 的设计目的它要保证某些 Profile 无论什么环境下都生效除非显式指定另一个 Profile 组合。不过这里有个细节需要说清楚spring.profiles.include 可以在不同的配置文件中分别声明。什么意思呢比如 application-dev.properties 里声明 spring.profiles.includecommon,monitorapplication-prod.properties 里声明 spring.profiles.includecommon,audit。当激活 dev 时加载的是 common 和 monitor当激活 prod 时加载的是 common 和 audit。这样 include 也能有一定的环境差异但它的依据是你是通过 active 进来的而不是外部直接传进来的。3. 实际项目里 include 的典型使用场景3.1 场景一公共模块拆分假设一个项目有数据库、Redis、MQ、日志、监控这几个模块。如果每个环境的 active Profile 里都把这几个模块的配置写一遍会产生大量重复代码而且一改配置就要改多个文件。我的做法是拆分为application-common.properties公共配置比如通用连接池参数、通用超时时间、通用日志格式。application-db.properties数据库相关配置。application-mq.properties消息队列相关配置。application-monitor.properties监控相关配置。然后在 application-dev.properties 里配置spring.profiles.includecommon,db,mq,monitorapplication-prod.properties 里同样配置spring.profiles.includecommon,db,mq,monitor不同的环境再覆盖一些差异化配置项。这样一目了然哪些模块是公共的哪些环境有特殊覆盖非常清晰。这个模式其实很像组件化的思想把系统拆成几个可组合的配置块然后用 include 把它们组合起来。3.2 场景二局部模块的按需启停还有一种场景是某个功能模块不是所有环境都需要。比如灰度环境需要开启流量复制模块但生产环境不需要。这种情况下application-gray.properties 里配置spring.profiles.includecommon,replicatorapplication-prod.properties 里只配置spring.profiles.includecommonactivgray 时流量复制模块生效activeprod 时不生效而且生产环境配置文件中根本看不到与流量复制相关的敏感配置。这种做法既保证了组合的灵活性也降低了误操作风险。3.3 场景三多个 Profile 叠加时的配置优先级梳理实际项目中往往不会只有一个 active。比如--spring.profiles.activedev,dev-detail这表示同时激活 dev 和 dev-detail。这种情况下dev-detail 中如果包含和 dev 相同的配置项哪个生效这里要注意多个 active 之间不是简单的先后关系。Spring Boot 对多 Profile 激活的处理是后面声明的 Profile 优先级更高。所以 --spring.profiles.activedev,dev-detail 中dev-detail 的配置优先级高于 dev。而 include 里的 Profile 会在所有 active 之前加载。所以整体优先级从低到高是include 引入的 Profile 先声明的 active Profile 后声明的 active Profile。这个优先级规则在生产环境调试时非常有用。比如你想在 dev 环境临时加一个特殊配置又不想改动公共配置就可以额外指定一个 --spring.profiles.activedev,dev-temp然后在 application-dev-temp.properties 中覆盖需要的配置项完全不影响 dev 原有的配置。4. 多 Profile 文件命名规则与加载机制4.1 命名规则回顾application-{profile}.properties要理解 include 和 active 的作用必须得先搞清楚 Profile 文件的加载机制。Spring Boot 默认加载文件名是 application.properties 或 application.yml。当激活某个 Profile 时它会额外加载 application-{profile}.properties 或 application-{profile}.yml。比如 spring.profiles.activedevSpring Boot 会去找 application-dev.properties如果同时激活了 dev 和 test则会加载 application-dev.properties 和 application-test.properties。你没有在配置文件中声明的 Profile比如直接放一个 application-xxx.properties 在 classpath 下但没激活 xxxSpring Boot 不会自动加载它。Profile 必须被激活才会生效激活手段主要是 active 和 include。这里有个容易忽略的细节include 指定的 Profile同样会遵循这个命名规则。比如 spring.profiles.includecommonSpring Boot 会去加载 application-common.properties。如果这个文件不存在会发生什么这里先留个悬念后面在常见问题里细说。4.2 YAML 与 Properties 的差异如果你的项目用的是 YAML 而不是 Properties情况会有一些细微的差别。在 YAML 中一个文件里可以通过---分隔多个文档块每个块可以指定自己的 spring.config.activate.on-profile。这种写法的实际效果和使用多个 application-{profile}.yml 文件类似。而 spring.profiles.active 和 spring.profiles.include 的语义是不分 YAML 和 Properties 的完全一致。唯一的注意点是如果你在单个 YAML 文件中用多文档块来组织 Profile那么 spring.profiles.include 写在哪个文档块里只对哪个文档块生效。这点和 Properties 文件中的全局生效有一些微妙差异。举个例子spring: profiles: dev profiles: include: common,monitor这个写法在旧版本中可能被解析成 spring.profiles 的配置但在 Spring Boot 2.4 之后spring.profiles这个属性被废弃取而代之的是spring.config.activate.on-profile。所以在高版本 Spring Boot 中YAML 里的多文档块现在推荐这样写spring: config: activate: on-profile: devinclude 的写法不变依然是spring.profiles.include。这里引出一个大坑在 Spring Boot 2.4 之前和之后配置文件的加载逻辑发生了重大变化这直接影响 include 的行为。下面来细说。4.3 Spring Boot 2.4 前后的行为差异Spring Boot 2.4 对配置文件的加载机制做了一次较大的重写最直接的影响有两个第一个影响是不再支持从application-{profile}.properties中读取 spring.profiles.active 来激活其他 Profile。什么意思在 2.4 之前的版本你可以在 application-dev.properties 中写 spring.profiles.activedev-extra然后 dev 被激活时 dev-extra 也会被激活。但在 2.4 中这个机制被移除了官方建议改用 spring.profiles.include 或 spring.profiles.group。第二个影响是spring.profiles.include 的优先级和加载顺序在 2.4 中有了一些调整但 include 先加载active 后加载 这个核心顺序仍然保留。官方文档对此有明确说明。还有一个重要概念是 spring.profiles.group。这个属性在 2.4 中引入目的是解决 include 的一个痛点include 只能在配置文件中写死而 group 可以更灵活地组合多个 Profile。5. spring.profiles.group 与 include 的组合使用5.1 group 是 include 的升级版spring.profiles.group 的定义方式如下spring.profiles.group.prodprod-db,prod-mq,common这段配置的意思是定义了一个名为 prod 的 Profile 组它包含 prod-db、prod-mq、common 三个成员 Profile。当激活 prod 时这三个成员 Profile 会被自动激活。与 include 相比group 的优势在于它把组合关系集中定义在独立的地方而不是分散在每个环境配置中。当你有很多环境、很多公共模块时group 的集中管理优势非常明显。而且 group 的优先级高于 include也就是说如果同时使用 group 和 includeSpring Boot 会先处理 group 的成员激活逻辑再处理 include 的激活逻辑。group 中定义的成员 Profile 会在 include 的 Profile 之前被处理这里我又需要谨慎一些——实际上 Spring Boot 的文档并没有给出非常明确的 group 与 include 相对优先级说明但从源码实现中来看group 的展开是在 Profile 激活阶段include 则是在配置文件加载阶段两者的实际生效顺序存在版本差异。为了稳妥我自己的实践经验是group 和 include 不要混用。要么全靠 include 组合要么全靠 group 组合。混用时出现优先级问题排查成本很高。5.2 什么时候用 group什么时候用 include如果你的项目是 Spring Boot 2.4并且 Profile 组合关系相对固定、且需要集中管理推荐用 spring.profiles.group。如果你的项目还在使用 Spring Boot 2.4 之前的版本或者你的 include 是分散在各个环境配置中的那维持 include 就好。从团队协作角度来看我也更推荐 group。因为 include 的写法天然会让人困惑到底是先加 include 再激活还是先激活再 include而 group 的语义非常明确这是一个组激活这个组等于激活这些成员。5.3 group 的配置示例与实际效果我举个真实例子一个典型的微服务项目包含以下 Profilecommon公共配置、db-dev开发库、db-prod生产库、mq-dev开发队列、mq-prod生产队列、monitor监控、audit审计使用 group 配置spring.profiles.group.devcommon,db-dev,mq-dev,monitor spring.profiles.group.prodcommon,db-prod,mq-prod,monitor,audit spring.profiles.group.testcommon,db-dev,mq-dev,monitor启动时只需要指定 --spring.profiles.activedev 或 prod不需要写一大串 Profile 名称。这个可读性和维护性比直接在启动命令里列一堆 Profile 要清晰得多。需要注意 group 的定义文件本身如果写在 application.properties 中则对所有环境生效如果写在 application-dev.properties 中则只有当 dev 被激活时这些 group 定义才可见。我建议把 group 定义统一放在主配置文件中避免分散。6. 核心实操一个完整的多环境配置改造案例6.1 改造前的现状与问题假设有一个老项目配置结构如下application.properties主配置application-dev.properties开发环境配置application-prod.properties生产环境配置每个环境配置文件中都大量重复定义了数据源、Redis、MQ 等配置。开发改一个公共参数比如连接池最大连接数就得同时改多个文件。更重要的是新接手的同事经常搞不清楚到底哪个配置在生效。改造目标抽取公共配置统一管理 Profile 组合尽量降低配置文件维护成本。6.2 改造步骤详解第一步把公共配置抽取出来。从 dev 和 prod 配置中找到不随环境变化的配置项比如 RocketMQ 的 producer 组名、公共的线程池参数、监控上报地址等放入 application-common.properties。第二步把差异化配置按环境保留。数据库 URL、Redis 地址等随环境变化的配置保留在 application-dev.properties 和 application-prod.properties 中。第三步在环境配置中声明 include。application-dev.properties 中添加spring.profiles.includecommonapplication-prod.properties 中添加spring.profiles.includecommon,audit第四步启动验证。使用 --spring.profiles.activedev 启动观察控制台输出确认加载了 application-common.properties 和 application-dev.properties。6.3 改造后遇到的一个优先级问题这里分享一个我改造过程中踩过的真实坑。抽取公共配置后连接池的配置如下application-common.properties 中配置了spring.datasource.hikari.maximum-pool-size10application-dev.properties 中配置了spring.datasource.hikari.maximum-pool-size5我预期结果是 dev 环境使用 5结果启动后 HikariCP 的连接池大小是 10。原因正是 include 的加载顺序include 的 Profilecommon先加载active 的 Profiledev后加载后加载的同名配置项应该覆盖先加载的。那为什么 dev 的值没生效排查后发现问题不在 include而在配置文件的覆盖顺序。Spring Boot 加载配置时遵循 profile-specific 配置覆盖默认配置 的规则默认 application.properties 的值被 application-common.properties 覆盖application-common.properties 的值被 application-dev.properties 覆盖——这个规则本身没错。但如果 application-dev.properties 中的配置项拼写有细微错误比如写成了 max-pool-size 而不是 maximum-pool-sizeSpring Boot 不会报错它只是不识别这个属性后面加载的同名属性就不会覆盖前面的值。所以排查优先级问题时第一件事不是怀疑 include 和 active 的顺序而是先用--debug参数启动查看实际生效的配置来源。Spring Boot 在 debug 模式下会打印出配置的来源信息哪个文件、哪个属性、哪个优先级一目了然。6.4 配置生效验证小技巧分享一个非常实用的验证方法写一个简单的 ApplicationRunner 或 CommandLineRunner在启动时打印关键配置值。Component public class ConfigPrinter implements ApplicationRunner { Value(${spring.datasource.hikari.maximum-pool-size}) private String poolSize; Override public void run(ApplicationArguments args) { System.out.println( poolSize: poolSize); for (String profile : Environment.getActiveProfiles()) { System.out.println( active profile: profile); } } }这个办法在排查 include、active、group 组合问题时非常好用。通过 Environment.getActiveProfiles() 可以直观看到当前到底激活了哪些 Profile避免我以为激活了 A其实还带着 B的误判。7. 常见问题与速查表7.1 include 指定的 Profile 文件不存在会怎样先说结论如果 spring.profiles.include 指定的 Profile 在配置文件加载时没有对应文件Spring Boot 不会报错。它只是简单地跳过这个 Profile 的属性加载。但这里有个细思极恐的坑如果这个 Profile 里定义了一个 bean比如 Configuration 类上标注了 Profile(common)但 common 这个 Profile 因为配置文件不存在而没有被正确激活那么这个 bean 就不会被注册。如果你在 include 中写了 common却又没有 application-common.properties 文件Spring Boot 的控制台可能不会出现明显的报错但你的某个功能就是起不来。所以我的建议是include 的 Profile 一定要保证有对应的配置文件哪怕文件里只有一行注释。养成这个习惯能避免很多深水炸弹。7.2 为什么 spring.profiles.include 不生效排查思路按以下顺序第一确认 Spring Boot 版本。2.4 之前和 2.4 之后的行为有差异老项目中 include 的用法在新版本中可能失效。第二确认 include 写在哪个文件中。include 写在 application.properties 中则全局生效写在 application-dev.properties 中则只有 dev 激活时生效。如果 dev 本来就没被激活你写在 application-dev.properties 中的 include 当然不会生效。第三确认拼写。spring.profiles.include 还是 spring.profiles.includes很多人会写错多写一个 s。第四确认是否与 group 混用且发生了预期外的覆盖关系。如果同时配置了 spring.profiles.group 和 spring.profiles.include建议先临时删掉 group 验证一步步缩小范围。7.3 active 和 include 同时配置同名属性到底谁的生效前面已经说过了include 的 Profile 先于 active 的 Profile 加载因此 active 同名属性覆盖 include 同名属性。举个例子application-common.propertiesapp.namecommon-nameapplication-dev.propertiesapp.namedev-nameapplication.propertiesspring.profiles.activedev, spring.profiles.includecommon最终生效的是 dev-name。如果把顺序反过来spring.profiles.activecommon然后 dev 通过 include 引入那最终生效的是 common-name。也就是说决定优先级的是 Profile 的加载顺序而不是你在 active 中写了几个 Profile。这个点非常容易搞混需要刻在脑子里。7.4 常见问题速查表症状可能原因处理方法include 的配置没生效include 写在非全局配置文件中且该 Profile 未激活检查 include 所在的文件和当前激活的 Profile 组合active 的配置被覆盖include 中同名配置项的加载顺序在 active 之前确认优先级规则调整配置归属文件代码里 Profile(xxx) 的类没有加载xxx Profile 未激活或 include 引入了不存在的 Profile用 Environment.getActiveProfiles() 打印确认Spring Boot 2.4 下 include 失效项目从旧版本升级加载机制已变更改用 spring.profiles.group某个环境配置始终是旧值配置文件拼写错误或属性未被识别使用 --debug 启动查看配置来源8. 我的经验总结这些配置习惯值得长期坚持我见过很多项目组在 Profile 的使用上陷入混乱核心原因不是技术不会而是没有提前约定好使用规范。从我自己的实操体验出发有几点想分享给大家参考。第一把入口配置和附加配置分开管理。spring.profiles.active 只负责指定主环境spring.profiles.include 只负责声明公共模块和附加模块。不要让一个配置既承担入口职责又承担附加职责否则排查时逻辑会很拧巴。第二多环境配置尽量采用公共配置 环境差异的模式。公共配置放在 application-common.properties 中环境差异配置放在 application-{env}.properties 中环境配置里用 include 引入 common。这套模式在微服务项目里我已经验证过很多次维护成本低新同事上手快。第三Spring Boot 2.4 的新项目建议直接用 spring.profiles.group 替代 include 作为主要组合手段。group 的优势在于集中定义、语义清晰尤其适合 Profile 数量较多的项目。第四定期用启动日志排查配置来源。每次人员变动、配置调整后留意启动时的 Profile 激活日志确认没有意外加载的 Profile。这比出问题后再去排查要划算得多。配置管理这件事投入产出比非常高。花半天时间把 Profile 结构理清楚后面每次发布、每次调参都能省下大把时间。这套东西不复杂但用好了能让项目的配置维护变得轻松很多。