升级到 2.4,服务起不来了
6 月初,把一个服务从 Spring Boot 2.3.7 升到 2.4.6。改完 pom 里的版本号,启动,直接炸:
***************************
APPLICATION FAILED TO START
***************************
Description:
Property 'spring.profiles' imported from location
'class path resource [application-prod.yml]' is invalid in a profile specific resource
[origin: class path resource [application-prod.yml] - 3:13]
Action:
See https://docs.spring.io/spring-boot/docs/2.4.x/reference/html/spring-boot-features.html
这次升级前我以为只是改个版本号的事,结果 2.4 的配置文件处理逻辑整个重写了。翻了官方文档和 release notes 才搞明白。
2.4 改了什么
Spring Boot 2.4 引入了一套新的配置文件加载机制,官方叫 Config Data API。它替换了原来的 ConfigFileApplicationListener。对我们影响最大的是三点。
变化一:spring.profiles 在多文档 YAML 里被废弃
我们原来的 application.yml 是这么写的(多文档格式,用 --- 分隔):
spring:
application:
name: order-service
profiles:
active: dev
---
spring:
profiles: dev # 2.4 里这么写会报错
server:
port: 8080
---
spring:
profiles: prod # 弃用
server:
port: 8081
2.4 要求改成:
spring:
application:
name: order-service
profiles:
active: dev
---
spring:
config:
activate:
on-profile: dev
server:
port: 8080
---
spring:
config:
activate:
on-profile: prod
server:
port: 8081
spring.profiles → spring.config.activate.on-profile。名字变长了,但语义更清楚:这是一个激活条件,不是"声明这个文档属于哪个 profile"。
变化二:profile-specific 文件里不能再激活别的 profile
这条限制是变化一带来的连锁反应。在 application-prod.yml 里写 spring.profiles.active 或 spring.profiles.include,2.4 会直接拒绝。
我们正好踩了:原来有个 application-prod.yml 里写了:
spring:
profiles:
include:
- redis-prod
- mq-prod
目的是激活 prod 的同时,把 application-redis-prod.yml 和 application-mq-prod.yml 也带上。2.4 里这写法在 profile-specific 文件中不允许。
报错:
Property 'spring.profiles.include' imported from location
'class path resource [application-prod.yml]' is invalid in a profile specific resource
三种改法:
# 改法一:把 include 挪到主文件(非 profile-specific)
# application.yml
spring:
profiles:
active: prod
include:
- redis-prod
- mq-prod
# 改法二:用 group(2.4 新增,推荐)
# application.yml
spring:
profiles:
active: prod
group:
prod:
- redis-prod
- mq-prod
test:
- redis-test
- mq-test
# 改法三:用多文档 + config.activate.on-profile,在文档里用 include
# application.yml
spring:
profiles:
active: prod
---
spring:
config:
activate:
on-profile: prod
profiles:
include: # 在 activate 文档里 include 是允许的
- redis-prod
我们最后用了改法二 spring.profiles.group。这是 2.4 新增的特性,把"激活 prod 等于激活哪一组"这件事收敛到一处,比散在各个文件里清楚太多。
变化三:新增 spring.config.import
这是个纯粹的新能力,可以引入额外的配置文件,而且优先级比当前文件高(后导入的覆盖先导入的)。
# application.yml
spring:
config:
import:
- classpath:common/redis.yml
- optional:file:/etc/order/override.yml # optional: 前缀表示文件不存在也不报错
- configtree:/etc/order/secrets/ # 配置树,K8s 挂载 secret 时很好用
configtree: 这个前缀我们后来用上了。K8s 里把 secret 挂载成目录,每个文件是一个配置项,文件名是 key,内容是 value:
/etc/order/secrets/
├── db-password # 文件内容就是密码
└── redis-password
直接就能用 ${db-password} 引用,不用再写环境变量映射。
升级前先跑一遍 migrator
这是官方提供的工具,能自动扫描并报告需要改的配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
启动时它会打印所有需要迁移的属性:
The use of configuration keys that have been renamed was found in the environment:
Property source 'applicationConfig: [classpath:/application-prod.yml]':
Key: spring.profiles
Line: 3
Replacement: spring.config.activate.on-profile
Property source 'applicationConfig: [classpath:/application-mq.yml]':
Key: spring.profiles.include
Line: 8
Reason: Profile specific files are not allowed to activate other profiles.
它甚至会在运行时帮你把旧属性自动映射到新属性上,所以带这个依赖可以先让服务跑起来,然后再逐个改。等全部改完,把这个依赖删掉。
我们就是这么做的:加上 migrator → 服务能正常启动 → 按报告逐个改配置 → 全改完 → 删掉依赖 → 重启验证。
兜底开关:legacy processing
如果一时改不完,2.4 保留了旧的处理逻辑,加一行配置就能回退:
spring:
config:
use-legacy-processing: true
我们中间态用过一周(有 3 个服务配置多,改不完),但官方明确说了这个开关会在未来版本移除,不能长期依赖。
我们实际改了什么
| 文件 | 改动 | 数量 |
|---|---|---|
| application.yml | spring.profiles → spring.config.activate.on-profile | 4 处 |
| application-*.yml | 移除 profile-specific 文件里的 include | 7 处 |
| application.yml | 改用 spring.profiles.group | 3 组 |
| application.yml | 新增 spring.config.import | 1 处 |
七个服务,累计改了不到 40 行配置,加 migrator 验证,一共花了两天。
另外两个 2.4 的变化
profile 的激活顺序影响最终值
2.4 里,后激活的 profile 覆盖先激活的。这个规则以前就有,但新版多文档格式让顺序更容易搞错。
spring:
profiles:
group:
prod:
- redis-prod
- mq-prod
- override # 放最后,它会覆盖前面的同名配置
我们专门加了一个 override profile 放在组末尾,用于线上临时调整参数,改它不会影响其他 profile 文件。
测试里的 @ActiveProfiles
单测里写 @ActiveProfiles("test") 的部分没受影响,但如果你在测试资源目录里也用了多文档 YAML,同样要改。我们有 12 个测试类的 application-test.yml 需要同步修改。
小结
- 2.4 重写了配置文件加载机制(Config Data API),
spring.profiles在多文档 YAML 中要改成spring.config.activate.on-profile。 - profile-specific 文件(
application-prod.yml)里不能再写spring.profiles.include,这是最常见的启动失败原因。 - 用
spring.profiles.group替代散落各处的 include,我们把"激活 prod 等于激活哪些"收敛到了一处。 - 升级前加
spring-boot-properties-migrator依赖,它会打印所有需要改的地方并在运行时临时兼容,改完再删掉。 - 来不及改就用
spring.config.use-legacy-processing=true回退,但别长期依赖。 spring.config.import和configtree:是 2.4 的好东西,K8s 挂 secret 时特别顺手。
这次升级真正的教训不是 2.4 改了什么,而是我们之前把 profile 的 include 关系散落在七八个文件里,所以改起来才这么麻烦。借这次升级把它们收敛成 spring.profiles.group 一处,算是因祸得福。