Spring Boot配置管理最佳实践
Spring Boot 提供了全面的外部化应用配置支持。它允许同一个应用制品(artifact)通过从各种来源提供值,从而在不同的环境中运行,例如:
- 属性文件
- 环境变量
- 系统属性
- 命令行参数
在本文中,我们将探讨管理 Spring Boot 应用配置的最佳实践。
一个设计良好的配置策略应确保:
- 配置与应用代码保持分离。
- 当必需的配置缺失或无效时,应用无法启动。
- 默认值可以被每个部署环境覆盖。
- 敏感值由专用的机密管理系统提供。
配置属性分类
通常,Spring Boot 应用配置分为三类:
- 应用默认值: 安全、非机密的值,例如第三方服务 URL、超时时间和重试次数限制。这些值随应用一起存储。
- 部署配置: 标识环境的值,例如数据库主机、队列名称和外部服务 URL。这些值通过部署平台提供。
- 机密信息: 密码、API 密钥、证书和私钥。这些值存储在专用的机密系统中。
例如,application.properties 可以提供应用默认配置属性:
默认值对于其可能被使用的每个环境都应该是安全的。数据库 URL 和凭据等属性绝不应硬编码在应用代码中。如果某个必需值没有安全的默认值,请在启动时验证其是否存在。
使用 @ConfigurationProperties 绑定应用属性
Spring 应用可以通过 Environment、@Value 或 @ConfigurationProperties 访问配置值。
当属性名称必须动态解析,或基础设施代码需要直接访问属性源时,请使用 Environment。
对于孤立的值,请使用 @Value:
分散的 @Value 表达式使得属性名称难以发现、验证和重构。使用 @ConfigurationProperties 的专用配置类型则支持所有这些特性。
对于相关的配置属性,优先使用 @ConfigurationProperties。它提供了:
- 类型安全的绑定和转换
- 属性名称与 Java 成员之间的宽松绑定
- 分组级校验
- 通过生成的元数据进行 IDE 补全和导航
例如,如果我们要与第三方 REST API 集成,可能需要配置服务的基 URL、超时时间和重试次数。
在上面的配置中,我们从环境变量 PROMOTION_SERVICE_URL 设置 base-url 的值,并从环境变量 PROMOTION_SERVICE_TIMEOUT 设置 timeout 的值,其默认值为 3 秒。
Spring Boot 支持基于 setter 的绑定。你可以将属性绑定到使用 setter 的类,如下所示:
使用 @ConfigurationPropertiesScan 注册配置类型:
@ConfigurationPropertiesScan 注解会扫描带有 @ConfigurationProperties 注解的组件,并将它们注册为 Spring Bean。
现在我们可以将 PromotionSvcProperties 注入其他 Spring Bean,并访问属性值。
优先使用 Record 进行 @ConfigurationProperties 绑定
通常,配置在启动期间建立,并在应用的整个生命周期内保持不变。
对于大多数应用配置,Java Record 是首选方案。它开箱即用地提供了不可变性,因此即使误操作也不会修改其值;相比之下,基于类的绑定可能会意外调用 setter:
Spring Boot 的宽松绑定会将规范的 kebab-case 名称(如 base-url)映射到 baseUrl 字段。
有时我们可能希望将属性绑定到第三方库提供的 Bean,但我们无法修改其源代码来添加 @ConfigurationProperties 注解。
要将配置属性直接绑定到第三方类,请将其声明为 @Bean,并在 Bean 方法上使用 @ConfigurationProperties 注解:
你可以如下配置 third-party.client 属性:
如果第三方类是不可变的或不支持 setter 绑定,请创建你自己的属性类,并使用它来构造第三方对象:
包装这种方式通常更可取,因为它可以避免将应用配置直接耦合到第三方库的类结构。
快速失败、尽早失败:在启动时验证配置
配置错误应在应用启动时被发现,并在配置缺失或无效时快速失败。在 @ConfigurationProperties Bean 上添加 @Validated,并对其属性应用 Jakarta Bean Validation 约束。
当 spring-boot-starter-validation 在 classpath 中时,绑定或校验失败会阻止应用启动。请验证必需值、数值范围、嵌套组以及其他应用级约束。
当必须区分缺失值与 Java 默认值时,请使用包装类型。例如,使用 @NotNull 注解的 Integer 可以标识缺失值,而 int 默认值为 0。
理解属性优先级
Spring Boot 会合并多个属性源。当同一属性出现在多个来源中时,优先级较高的来源会提供最终生效的值。
以下简化顺序展示了应用部署中最常用的属性源,按从低到高的优先级排列:
当排查某个值与预期配置值不一致的问题时,Spring Boot 配置加载的优先级就很重要。
环境变量受到操作系统、容器运行时和云平台的广泛支持。Spring Boot 会根据规范化属性名称推导环境变量名称:将点替换为下划线、移除短横线,并将结果转换为大写:
当一个属性在多个配置源中定义时,确定其有效值可能很困难。IntelliJ IDEA 可以将已解析的配置值以内联提示(inlay hints)的形式显示在编辑器中。选择某个提示可以识别提供该值的属性源,并显示该值是否被其他源(如环境变量或系统属性)覆盖。
IntelliJ IDEA 还提供属性声明、@ConfigurationProperties 成员和属性使用之间的导航。对于自定义配置属性,spring-boot-configuration-processor 生成的元数据增强了这一支持。
将机密信息存储在专用系统中
不要将密码、API 密钥、证书或私钥存储在源代码控制中。请使用诸如 HashiCorp Vault、AWS Secrets Manager、Google Cloud Secret Manager、Azure Key Vault 等系统,或等效的平台服务。
确保机密信息不会出现在日志、错误消息、配置元数据和可公开访问的管理端点中。
注意: 在非生产环境中,Actuator 的 env 端点可以帮助识别某个有效属性的来源。该端点不应公开暴露,因为配置可能包含敏感信息。
推荐的配置管理方式
没有一种适用于所有应用的单一配置管理方法。请根据应用的架构、部署环境和复杂性选择策略。
单体应用
对于单体应用,将共享默认值保留在应用中,仅在必要时使用特定于 profile 的配置文件,并通过环境变量提供特定于部署的覆盖值。将敏感值存储在专用的机密管理器中。
容器化工作负载
对于运行在 Kubernetes 等容器平台上的工作负载,请在应用中保留合理的默认值,并通过 ConfigMaps 提供特定于部署的配置。将机密信息单独存储在专用的机密管理系统中。
微服务
对于微服务架构,可以考虑使用 Spring Cloud Config Server 来集中管理配置、治理和版本控制。继续通过专用的机密管理系统管理机密信息。
总结
有效的应用配置始于合理的默认值、类型安全的 @ConfigurationProperties 和启动时校验。将特定于环境的值保留在应用之外,理解属性源的优先级,并将机密信息存储在专用的机密管理系统中。
正确的配置策略应反映应用的架构和部署环境。
在本地开发和远程调试期间,IntelliJ IDEA 通过显示已解析的属性值及其来源、突出显示覆盖,并提供配置文件与绑定的 Java 属性之间的导航,帮助你了解生效的配置。