每个Java应用都需要配置。数据库连接、服务端口、外部系统地址、功能开关——这些因环境而异的值不应该硬编码在代码中。配置管理看似简单,但随着系统复杂度的增加,它逐渐成为一个需要深思熟虑的工程问题。
参考:https://xrzqr.cn/category/city-forecast.html
Java配置管理的起点是properties文件。key=value的格式简单直观,JDK原生支持。对于简单应用,properties文件足够。但它的局限也很明显:不支持层次结构,复杂配置需要变通(如database.url、database.username);不支持类型,所有值都是字符串;没有标准的占位符机制。
YAML的出现在一定程度上解决了这些问题。它天然的层次结构让数据库配置可以嵌套;支持列表、对象等复杂类型;---分隔符支持一个文件包含多文档。Spring Boot对YAML的支持让它在Java生态中流行起来。但YAML也有缺陷——对缩进敏感,复制粘贴时容易出错;大文件不易阅读。
无论使用properties还是YAML,配置文件都需要随应用一起打包。这就带来了问题:配置与代码耦合,不同环境的配置需要不同构建(或构建后替换)。常见的解决方案是使用占位符和Profile。Spring Boot的Profile机制允许为不同环境定义不同的配置文件(application-dev.properties、application-prod.properties),通过spring.profiles.active激活。
随着微服务架构的普及,配置文件的数量激增。几十个服务,每个服务有多个环境,手动管理这些配置变得困难。配置中心应运而生。Spring Cloud Config Server、Apollo、Nacos、Consul等配置中心将配置从应用中抽离出来,集中存储和分发。
配置中心的价值在于:配置变更无需重新构建和部署应用;配置可以版本化和审计;不同环境、不同服务可以共享和覆盖配置;敏感配置可以加密存储。在微服务架构中,配置中心几乎是标配。
参考:https://xrzqr.cn/category/national-weather.html
但配置中心也带来了新的问题。它引入了额外的依赖——应用启动时需要能访问配置中心,否则无法启动。配置中心本身需要高可用部署,增加了运维复杂度。对于小型项目或单体应用,配置中心可能是过度设计。
配置管理的演进趋势是"不可变配置"。这个概念来自十二要素应用宣言:配置应该存储在环境变量中,而不是配置文件中。环境变量与语言和平台无关,更容易在不同环境中管理;不会意外签入版本控制;更容易在容器编排系统中注入。
在实际Java应用中,环境变量通常与配置文件结合使用。配置文件定义默认值,环境变量覆盖这些默认值。Spring Boot的宽松绑定规则支持将环境变量映射为配置属性——如DATABASE_URL可以映射为database.url。
配置的类型安全是一个常被忽视的问题。在代码中通过@Value或environment.getProperty()获取配置,返回值是字符串或需要转换的基本类型。这种方式容易出错——配置键拼写错误在运行时才发现,类型转换失败在运行时才暴露。
Spring Boot的@ConfigurationProperties是解决这个问题的利器。它允许将一组配置绑定到类型安全的Java对象上。配置键写错在编译时就无法绑定;支持复杂嵌套和校验注解;IDE可以提供自动补全。使用@ConfigurationProperties是配置管理的最佳实践之一。
配置的校验应该在应用启动时进行,而不是在使用时。required属性可以标记必需配置;@NotNull、@Min、@Max等校验注解可以验证配置值的合法性;自定义校验器可以实现更复杂的业务规则。启动时校验失败应该快速失败(fail-fast),避免带着错误配置运行。
敏感配置(密码、密钥、令牌)不应明文存储在配置文件或环境变量中。Vault、KMS等密钥管理服务提供了安全存储和动态获取敏感配置的能力。在Kubernetes环境中,Secret对象可以存储敏感数据并挂载为文件或环境变量。
配置的变更管理是另一个实践问题。配置变更应该有审批流程,记录变更原因和变更人;配置变更应该有回滚能力,错误的配置能快速恢复;配置变更应该有灰度能力,先在测试环境验证,再在生产环境分批生效。
配置管理没有"银弹"。简单的单体应用用properties文件就够了;复杂微服务系统需要配置中心;Kubernetes原生环境可能使用ConfigMap和Secret。选择哪种方案,需要基于团队规模和基础设施能力。
配置管理的核心原则是:配置与代码分离,环境与代码无关。遵循这个原则,可以让应用更可移植、更安全、更易于运维。
参考:https://xrzqr.cn