news 2026/10/13 7:29:11

Spring Boot配置管理:active与include的加载顺序与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot配置管理:active与include的加载顺序与实战

1. 项目启动时的环境切换难题:从一份配置拆成多套说起

先从一个我再熟悉不过的场景讲起。刚接触 Spring 项目时,所有人的配置都堆在同一个application.properties里:数据库地址写的是本机的localhost:3306,缓存连接指向开发服务器,第三方接口的密钥也是开发环境那一套。等到要部署测试环境或者生产环境时,就把配置里的地址和口令逐行替换,改完再启动,运气好一遍过,运气不好漏掉某一个配置项,项目能跑但功能全错,排查起来相当浪费时间。

后来接触到spring.profiles.active,才意识到这种"改配置再重启"的做法从一开始就是错的。Spring 的 profile 机制,本质上就是为同一套代码维护多份运行环境配置的手段:开发、测试、预发、生产各自维护独立的配置文件,启动时通过激活对应的 profile 来决定加载哪份配置。它的设计目标,是让"环境切换"这件事变成一行参数的事,而不是十几处手改的事。

不过,真正上手之后你会发现,spring.profiles.active只是解决了"选哪组配置"的问题,而spring.profiles.include解决的却是"除了当前选的这组,还要把另外几组配置一并带上"的问题。两者在配置文件中只差一个单词,但行为差异很大,用错的情况我也见过不少。尤其在一些多模块、多环境的项目里,如果搞不清楚这两个属性的加载顺序和组合逻辑,配置越写越乱,最后谁也不敢动配置文件。

所以这篇内容我不想只讲 API 层面的用法,更想把这几年实际项目中踩过的坑、总结出的配置组织方式一起聊清楚,让有同样困扰的人少走点弯路。

1.1 配置文件的分层直觉

先建立一个直观认知。可以把 profile 配置理解成"基础材料 + 场景配料"的关系。基础的application.yml放的是所有环境里都不会变的公共配置,比如服务名、端口通用的部分、框架版本相关的参数。而每个独立的 profile 配置文件,比如application-dev.yml、application-prod.yml,放的是某个环境特有的内容。

此时spring.profiles.active就是"今天我要做哪个版本的菜",它负责决定最终激活哪一份配料。spring.profiles.include则是"除了主配料,我还要额外加哪些辅料",它在不改变主激活环境的前提下,让其它 profile 的配置也一起生效。

理解到这一层,后面看两个属性的具体行为就容易多了。

2.spring.profiles.active的使用姿势与生效逻辑

spring.profiles.active的作用一句话可以讲清楚:设置当前激活的 profile 名称,Spring Boot 启动时会额外加载对应的application-{profile}.yml文件。多个 profile 用逗号分隔,例如spring.profiles.active=dev,local。

但实际使用中,它的细节比表面上一行配置要多得多。

2.1 最基础的用法:逗号分隔多个环境

假设项目里有这样的文件结构:

src/main/resources/ ├── application.yml ├── application-dev.yml ├── application-test.yml └── application-prod.yml

在application.yml中写:

spring: profiles: active: dev

启动后 Spring Boot 加载application.yml作为主配置,再加载application-dev.yml作为 profile 专项配置。两者同时生效,且专项配置中的属性值会覆盖主配置中同名属性值。

如果你想同时激活多个 profile,比如既要开发环境,又要本地专用的一些 mock 配置,可以写:

spring: profiles: active: dev,local

注意逗号分隔的多个 profile 之间是平级关系,它们在配置加载阶段都会被激活,没有"主次"之分。但如果两个 profile 专项文件里定义了同一个属性,后加载的会覆盖先加载的。从这个角度说,多个 profile 同时激活时,文件加载顺序值得单独确认,后面我会专门讲这一块。

2.2 外部覆盖的优先级:命令行、环境变量、Java 系统属性

这是spring.profiles.active最有价值的特点之一:写在application.yml里的激活值,并不是最终值,它是可以被外部更高优先级的配置来源覆盖的。

命令行参数优先级最高,启动时这样指定:

java -jar app.jar --spring.profiles.active=prod

也可以通过环境变量指定:

export SPRING_PROFILES_ACTIVE=prod java -jar app.jar

还可以用 Java 系统属性:

java -Dspring.profiles.active=prod -jar app.jar

这个能力的实际意义在于:项目构建产物是同一个 jar 包,开发、测试、生产环境不需要重新打包,只需要在启动命令里指定不同的 profile 就行。这也是很多部署脚本最常用的一行参数。

我把几个常见配置来源按优先级排一下,方便参考:

配置来源优先级常见写法
命令行参数最高--spring.profiles.active=prod
Java 系统属性高-Dspring.profiles.active=prod
环境变量中SPRING_PROFILES_ACTIVE=prod
application.yml内部低spring.profiles.active: dev

一个容易被忽略的点是:正因为 active 可以被外部覆盖,很多部署平台(无论是容器编排还是云平台)的教程都会强调用环境变量来动态切换环境,但这同时也意味着,如果有人在服务器上设置了SPRING_PROFILES_ACTIVE环境变量而没有及时清理,你写在application.yml里的 active 就会一律失效。这种环境变量"残留"问题,我在团队里排查过不止一次。

2.3 active 写在配置文件中要注意的两个关键点

第一,spring.profiles.active不应该出现在 profile 专项配置文件里。什么意思?就是不要写在application-dev.yml里再写spring.profiles.active: test,这种写法在新的 Spring Boot 版本里要么无效,要么报错。因为 profile 专项文件本身就是由 active 或 include 触发而加载的,你在这个文件里又想去激活别的 profile,就变成无限套娃式的逻辑。正确做法是:激活动作只放在主配置文件或外部参数中。

第二,spring.profiles.active写多个值时的覆盖行为要小心。如果命令行传的是--spring.profiles.active=prod,配置文件里写的是dev,最终生效的是命令行传的prod,两者不会合并。也就是说,外部传参采用的是"整体覆盖"而不是"追加"。如果你本来期望外部传参只是在原有基础上多激活一个环境,这种思路行不通,需要配合 include 来实现。

3.spring.profiles.include:主动"拉入"其它配置组的叠加能力

如果说 active 是"选主菜",include 就是"加辅料"。spring.profiles.include的作用,是在当前配置生效的同时,把其它指定的 profile 一并加载进来,而且不影响当前已激活的 profile 集合。

它最典型的应用场景是:某些配置是多个环境共用的基础能力,比如监控上报参数、统一的日志采集配置、Redis 缓存底座的通用连接参数。这些内容单独抽成一个 profile 文件,比如application-common.yml,然后通过 include 引入。

3.1 include 的语法与典型场景

在application.yml中:

spring: profiles: include: common,monitor profiles: active: dev

启动时,如果 active 指定的是 dev,那么最终激活的 profile 集合是:dev, common, monitor。集合并集里都生效了,但外部通过--spring.profiles.active=prod覆盖时,dev会被替换成prod,而 include 引入的common, monitor仍然保留。

这个"外部覆盖 active 不影响 include"的特点非常实用。比如同一个应用在生产环境跑的时候,不管环境怎么切换,监控上报和统一日志这两块基础配置始终都要带着,你就不需要每次启动都手动加参数了,把它们写死在 include 里更稳妥。

实际项目中我见过两种组织方式。一种是把公共能力拆成独立 profile 文件,用 include 引入作为默认组合:

spring: profiles: include: common,logging,monitor

另一种是在 profile 专项文件里动态 include。比如application-cloud.yml里写:

spring: profiles: include: registry,mysql-cloud

这样当别人主动激活cloud这个 profile 时,registry和mysql-cloud会被连带激活,而这两个 profile 本身不用出现在主配置的 activate 里。这种写法在区分部署形态(本地、服务器、云环境)时尤其方便。

3.2 include 在 YAML 多文档中的行为

如果你用过 Spring Boot 2.4 之后版本的 YAML 多文档功能,应该见过这种写法:

spring: config: activate: on-profile: dev --- spring: config: activate: on-profile: prod

在这种结构下,spring.profiles.include放在哪个文档里,行为很不一样。它如果放在默认的第一个文档(没有 on-profile 条件)中,就是全局的,任何环境都会引入它;如果放在某个on-profile: xxx的文档里,只有当对应的 profile 被激活时,这个 include 才会生效。

我建议在 YAML 多文档结构下,把 include 尽量放在默认文档的顶部,让它保持全局语义。否则后续维护的人看到某个环境莫名其妙多加载了配置,要翻到文档中间才能发现原因,排查成本很高。

另一点值得说的是,include 虽然可以写在命令行参数里(--spring.profiles.include=xxx),但它和配置文件里的 include 是覆盖关系还是叠加关系,取决于属性来源的优先级规则。由于这个行为在不同版本之间并不稳定,我在实际团队里定过一个规矩:include 只放在配置文件里维护,命令行不传 include 参数,避免不确定的行为影响环境一致性。

3.3 include 与 group:什么时候该用哪个

Spring Boot 2.4 之后还引入了spring.profiles.group,它在一定程度上比 include 更贴近"组合激活"的场景。group 的语义是:激活某个 profile 时,自动一次性激活一整组其它 profile。典型写法:

spring: profiles: group: dev: dev-db,dev-mq,dev-cache prod: prod-db,prod-mq,prod-cache

这样做的好处是,启动时只需要--spring.profiles.active=dev,group 会自动帮你把dev-db、dev-mq、dev-cache全部激活。相比 include,group 更强调"按主环境分组",而 include 更强调"跨环境共用的基础能力"。

我的实践体会是:group 适合把同一个环境下的子模块配置归组,include 适合把多个环境都需要的公共配置统一引入。两个配合使用时,尽量让职责清晰:include 管基础能力,group 管环境分组,不要混着用。如果都往里面堆,最后 profiles 集合完全不可预期。

4. active 和 include 的核心区别:激活 vs 叠加

很多文章会直接把两者的区别归结为"active 设置当前激活的环境,include 追加额外的环境",这句话对,但不够深。真正理解它们,要从两个维度看:一个是"它影响的是什么集合",另一个是"它在什么时候生效、可不可以被外部覆盖"。

4.1 两行配置,行为差异很大

先看一张对比表:

对比维度spring.profiles.activespring.profiles.include
核心语义设置当前主激活 profile追加加载其它 profile
是否影响最终激活集合的主标识是,它就是主标识否,只是在集合中加入其它项
典型位置主配置文件 或 外部参数主配置文件 或 profile 专项文件
外部覆盖行为高优先级参数会整体覆盖配置值通常由配置文件维护,外部传参有覆盖风险
加载顺序先被读取,决定后续加载哪些 profile-specific 文件与 active 一起参与拼接,确定最终加载集合
典型使用场景环境切换:dev/test/prod公共能力引入:logging/monitor/common
能否在 profile 专项文件中声明不推荐,新版本基本无效可以,但仅在该 profile 激活时生效

这张表里最值得反复琢磨的是第一行和第五行。active 是"因",include 是"果"的一部分。Spring Boot 启动时,先扫描所有配置来源,确定 active 的最终值(外部参数优先),然后才是进入配置加载阶段,把 active 指定的 profile 专项文件和 include 指定的 profile 专项文件一起纳入加载列表。

4.2 叠加顺序与覆盖优先级

当多个 profile 同时激活时,一个绕不开的问题是:同名属性谁覆盖谁。

官方文档给出的加载优先级里,profile 专项配置文件的优先级高于无 profile 的主配置文件。也就是说,无论多少个 profile 被激活,它们的优先级都高于application.yml。但多个 profile 文件之间的相对顺序,则取决于它们进入加载列表的顺序。

这个顺序规则大致是:spring.profiles.active中先列出的 profile 先加载,之后是spring.profiles.include引入的 profile。后加载的 profile 文件覆盖先加载的。举个实际例子:

spring: profiles: active: dev include: common

启动后加载顺序是:application.yml->application-dev.yml->application-common.yml。最终属性值以common中的为准。所以如果dev和common里都定义了同一个第三方接口地址,common里会赢。

这个顺序经常被搞反。很多人在dev里精心调好的参数,测试时发现不生效,排查到最后才发现是common里的同名配置在后面覆盖了。我的建议是:尽量不要让不同的 profile 文件定义同名属性,如果实在避免不了,必须清楚加载顺序,把"最终生效值"的可预期性放在第一位。

4.3 容易混淆的加载过程拆解

用一个场景完整走一遍加载过程。假设配置如下:

application.yml中:

server: port: 8080 spring: profiles: active: dev include: common

application-dev.yml中:

server: port: 8081 logging: level: root: debug

application-common.yml中:

app: timezone: UTC+8

启动命令为:

java -jar app.jar --spring.profiles.active=prod

最终行为是这样的:

  1. bootstrap 阶段,Spring Boot 收集配置来源。命令行中指定的--spring.profiles.active=prod优先级高于application.yml中的dev,所以最终主激活变成prod。
  2. 由于 include 没有外部参数覆盖,common依旧保留,最终的激活集合是prod, common。
  3. 加载配置时,除了application.yml,还会找application-prod.yml和application-common.yml。application-dev.yml不会加载。
  4. 如果application-prod.yml不存在,不会报错,只是没有对应专项配置。这一点经常让新手困惑——active 指定了一个不存在的 profile,启动照样能成功,除非该 profile 里有必填配置,否则只能到运行时才发现问题。

这个拆解里,最值得记住的是第二步:active 可以被外部整体覆盖,include 是配置内部声明的追加项,两者不是同一个维度的东西。理解了这个,很多配置异常都能快速定位。

5. 实战组合:一套可靠的 profile 组织方案与踩坑记录

理论讲完,说点落地经验。我所在的团队以前维护的配置是这样组织的,用了之后环境切换和公共配置管理都清爽了很多,分享出来供参考。

5.1 推荐的分层结构

结合 active 和 include 的特点,我把配置拆成三层:

第一层是主配置application.yml,只放所有环境都一致的项,外加两个关键声明:include 引入公共能力 profiles,group 定义环境分组。

spring: application: name: demo-service profiles: include: common,logging group: dev: dev-db,dev-cache prod: prod-db,prod-cache

第二层是公共能力 profile 文件,按功能拆分:application-common.yml放公共业务参数,application-logging.yml放日志级别和格式。这样可复用的配置不会散落在每个环境里重复维护。

第三层是环境相关 profile 文件:application-dev-db.yml、application-prod-db.yml分别管理各自的数据库地址;application-dev.yml、application-prod.yml则作为环境入口,可以再声明一些环境级的覆盖参数。

运行开发环境时启动命令只需要:

java -jar app.jar --spring.profiles.active=dev

因为 group 的定义,dev-db、dev-cache会跟着激活;因为 include 的定义,common、logging也会跟着激活。最终配置来源清晰,每个文件职责单一,不会再出现"一个配置改动影响所有环境"的尴尬局面。

5.2 我踩过的几个坑

这个组织方式不是一开始就这样的,中间踩过不少坑,挑几个典型说说。

第一个坑是 include 引入的 profile 文件里又写了spring.profiles.include。在项目快速迭代期,有人在application-common.yml里又 include 了common-ext,看起来逻辑没问题,但一旦某个文件的 include 链路过深,排查配置来源就变成一场灾难。更麻烦的是,如果两个 profile 互相 include(A include B,B include A),Spring 启动会直接报循环引用错误,排查起来非常绕。后来我们定了一条铁律:include 只在主配置文件中写,profile 专项文件里一律不写 include。

第二个坑是 group 与 include 同时使用时的加载顺序问题。之前有个同事把 group 中的子 profile 又写进了 include,结果同一个 profile 在同一份配置里被加载了两次,因为顺序不同,同名属性覆盖结果变得不确定。从那以后,我们约束:公共能力进 include,环境子项进 group,两者集合不允许有交集。

第三个坑是环境变量覆盖。有一次测试环境一直加载到了生产配置,查了半天才发现是环境里残留了SPRING_PROFILES_ACTIVE=prod的环境变量,导致application.yml里的 dev 全部失效。部署脚本里如果曾经设置过环境变量,后面项目切换了环境或调整了命名,旧变量特别容易残留下来。现在的处理方式是:部署脚本启动前显式打印当前生效的 profile 值,每次发布时对比确认。

5.3 小团队落地建议

最后给一些执行层面的建议。如果你所在的团队不大,配置的管理完全不需要复杂,记住三条原则就够了。

第一,spring.profiles.active的最终值应该尽量只由启动命令或环境变量控制,不要依赖写在配置文件里的那一份。因为只有这样才能保证同一个包在不同环境的表现是完全可控的。配置文件里的 active 就当默认值对待,别当成"唯一指定值"。

第二,spring.profiles.include一定要控制使用范围。它最适合的是"凡是这个服务启动都必须带上的基础能力",而不是"某个环境特有的配置"。凡是环境特异的,交给 active 或 group。

第三,每个 profile 的名字要有明确约定。我看到过dev、dev1、dev-new、local-dev这类名字在同一个项目里共存的情况,最后配置文件数量爆炸,谁都不敢删。profile 名称应该是一份配置仓库的公共契约,改名要慎重,新增要评审。

配置管理这件事,看着只是几行 YAML,但真正复杂的从来不是语法,而是多人协作下每个属性值的来源能不能被快速追溯。合理利用 active 和 include 各自的特性,让每个配置文件的职责边界清晰起来,后续维护的幸福感会提升很多。

这两年在不少项目里看到过因为 profiles 使用混乱导致的线上配置错乱问题,大多不是技术上的疑难杂症,而是对两个属性边界理解不透,又在配置文件里层层叠加。回到开头的问题:spring.profiles.active和spring.profiles.include的区别,本质上就是一句话——前者决定"用哪组环境配置",后者决定"在任何环境下都要把哪些配置带上"。把这句话理解到位了,具体环境怎么组织,遇到已知的坑怎么规避,就会有一个非常清晰的判断基准,不需要再去查文档记各种边界情况了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 7:29:07

Codex桌面端接入本地模型:零API消耗的AI编程环境搭建指南

1. 从积分焦虑说起:这套方案的适用场景与选型逻辑先说个让我印象挺深的事。有位朋友做一个小工具的原型验证,需求不复杂,就是用对话界面让 AI 帮忙写点胶水代码、调一调脚本。结果一周下来,后台账单跑出去不少零花钱。他跑来问我&…

作者头像 李华
网站建设 2026/10/13 7:28:57

SpringBoot + Shiro 实现细粒度动态权限系统设计与实战

先说个场景:同一个订单列表接口,总部运营登录后能看到全部订单、客户手机号原文和成本价;区域经理只能看到所属区域的订单,手机号中间四位要打码,成本价更不能显示;普通销售员只能看到自己名下订单&#xf…

作者头像 李华
网站建设 2026/10/13 7:28:45

Flask+SQLite宿舍分配系统:房态一致性与实时同步实战

简介:Hostel-Allocation 是一个面向高校信息化管理场景的宿舍在线预订全栈Web系统,专为计算机专业学生课程设计、毕业项目或轻量级校园应用开发实践打造,解决传统人工排号导致的效率低、接触多、管理难等痛点。资源包共22个文件(3…

作者头像 李华
网站建设 2026/10/13 7:28:28

AI辅助DevOps实战:从CI脚本到日志排障的效率提升

1. 拆解需求:AI 进 DevOps 到底要解决什么问题先把话说在前面:我并不是在鼓吹"AI 要取代 DevOps 工程师",恰恰相反,我自己在 GitLab CI、Jenkins Pipeline 和日志系统上踩了几年坑,最清楚这些活里有多少是重…

作者头像 李华
网站建设 2026/10/13 7:28:10

IP地址与二进制:从进制转换到子网划分的实战指南

搞网络的人,几乎每天都会跟IP地址打交道。配个静态IP、规划一个网段、排查一次网络不通,都绕不开它。但很多朋友对IP地址的理解停留在“四个数字,用点隔开”这一层,真要让你现场说说192.168.1.10/24这个地址背后的二进制长什么样&…

作者头像 李华
网站建设 2026/10/13 7:27:03

Linux命令实战指南:从底层逻辑到高频组合

1. 先从“为什么总是记不住命令”说起这几年带过不少新人,也帮很多半路转行的朋友补过 Linux 基础。大家普遍有一个困惑:命令太多了,背了又忘,忘了又背,键盘敲得稀碎。其实问题不在于你的记忆力,而是多数教…

作者头像 李华