SpringNotes

第 06 章:配置与 Environment

zjc 于 2026-01-06 发布

这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 配置管理决定应用在不同环境中的行为。Spring Environment 把配置源抽象成 PropertySource,并通过统一的解析、优先级和类型转换机制提供给 Bean。

6.1 配置来源

常见来源:

命令行参数
SPRING_APPLICATION_JSON
环境变量
系统属性
application-{profile}.yml
application.yml
@PropertySource
默认值

不同版本和配置中心的优先级可能不同。确认优先级的可靠方式是输出 Environment 的 PropertySources。

6.2 Environment 抽象

@Component
public class ConfigPrinter {
    private final ConfigurableEnvironment environment;

    public ConfigPrinter(ConfigurableEnvironment environment) {
        this.environment = environment;
    }

    public void print() {
        environment.getPropertySources().forEach(ps ->
                System.out.println(ps.getName() + " = " + ps.getSource()));
    }
}

核心接口:

接口 作用
PropertyResolver 读取和解析属性
Environment 环境属性与 Profile
ConfigurableEnvironment 可修改 PropertySource
PropertySource 单个配置来源
MutablePropertySources 有序配置源集合

6.3 application.yml

spring:
  application:
    name: order-service
  datasource:
    url: jdbc:mysql://localhost:3306/order?useSSL=false
    username: order
    password: ${DB_PASSWORD}
  jpa:
    open-in-view: false

pay:
  url: https://pay.example.com
  timeout: 3s
  retry-times: 2

推荐:

  1. 使用 kebab-case
  2. 有单位的时间用 Duration
  3. 敏感信息来自环境变量或密钥系统;
  4. 每个环境只写差异配置;
  5. 配置键集中到 Properties 类。

6.4 Profile

spring:
  profiles:
    active: dev
---
spring:
  config:
    activate:
      on-profile: dev
pay:
  url: http://localhost:8081
---
spring:
  config:
    activate:
      on-profile: prod
pay:
  url: https://pay.example.com

激活方式:

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

不要用 Profile 表达业务开关,例如 feature-pay-v2。Profile 适合环境差异,业务开关应有自己的配置键和灰度体系。

6.5 配置绑定

推荐 @ConfigurationProperties

@ConfigurationProperties(prefix = "pay")
public record PayProperties(
        URI url,
        Duration timeout,
        int retryTimes,
        List<String> fallbackRegions) {
}

启用:

@ConfigurationPropertiesScan
@SpringBootApplication
public class Application {
}

校验:

@Validated
@ConfigurationProperties(prefix = "pay")
public record PayProperties(
        @NotNull URI url,
        @NotNull Duration timeout,
        @Min(0) @Max(5) int retryTimes) {
}

优点:

  1. 配置集中;
  2. 类型安全;
  3. IDE 提示;
  4. 启动时校验;
  5. 易写单元测试。

6.6 松绑定

以下写法都可以绑定到 pay.retryTimes

pay.retry-times=3
pay.retryTimes=3
PAY_RETRY_TIMES=3

环境变量通常使用大写下划线:

SPRING_DATASOURCE_URL
PAY_RETRY_TIMES

注意:

  1. 不要混用多种风格;
  2. Map 和 List 的绑定规则更细;
  3. 缩短环境变量名需要显式配置;
  4. 环境变量值都是字符串,绑定到集合或对象时要看清规则。

6.7 占位符与默认值

pay.url=${PAY_URL:https://default.example.com}
app.name=${spring.application.name}
app.description=${app.name} order backend

支持 SpEL 的场景要谨慎使用:

app.token-ttl=#{60 * 60}

过度使用表达式会让配置难以追踪。简单占位符优先。

6.8 配置中心

本地配置之外,微服务常使用:

方案 特点
Spring Cloud Config Git 后端,配置版本化
Nacos 注册与配置一体
Consul 多云生态常用
Kubernetes ConfigMap 平台原生
Vault 密钥管理

优先级建议:

本地代码:默认值和非敏感配置
环境变量:简单覆盖
配置中心:环境差异和动态开关
密钥系统:密码、证书、Token

不要把数据库密码提交到 Git,也不要把所有业务配置都堆进环境变量。

6.9 动态配置

配置中心刷新示例:

@RefreshScope
@Component
public class RiskClient {
    private final String endpoint;

    public RiskClient(@Value("${risk.endpoint}") String endpoint) {
        this.endpoint = endpoint;
    }
}

刷新后 @RefreshScope Bean 会被重建。要注意:

  1. 旧 Bean 中持有的状态会丢失;
  2. 依赖它的对象引用可能仍指向旧代理目标;
  3. 动态开关应原子读取;
  4. 重要配置变更要有审计和灰度;
  5. 连接类配置重建成本高。

更简单的动态开关可以自己维护内存快照:

@Component
public class FeatureFlags {
    private final AtomicReference<Map<String, Boolean>> flags =
            new AtomicReference<>(Map.of());

    public void update(Map<String, Boolean> next) {
        flags.set(Map.copyOf(next));
    }

    public boolean enabled(String key) {
        return flags.get().getOrDefault(key, false);
    }
}

6.10 配置安全

常见风险:

  1. 明文密码提交仓库;
  2. Actuator 暴露 env;
  3. 日志打印配置对象;
  4. 配置中心权限过宽;
  5. 配置变更没有审计;
  6. 测试配置误用于生产;
  7. 默认账号密码未修改。

建议:

  1. 使用 Vault、KMS 或云 Secret;
  2. 日志脱敏;
  3. 限制 Actuator 端点;
  4. 配置中心按命名空间授权;
  5. 保存配置变更历史;
  6. 启动时校验生产必需键;
  7. 本地开发配置与生产密钥隔离。

6.11 排查配置问题

@Component
public class EnvironmentDiagnostics {
    public void print(String key) {
        Environment env;
    }
}

更直接的做法:

public void print(ConfigurableEnvironment env) {
    System.out.println(env.getProperty("pay.timeout"));
    env.getPropertySources().stream()
       .filter(ps -> ps.containsProperty("pay.timeout"))
       .forEach(ps -> System.out.println(ps.getName()));
}

Actuator 端点:

/actuator/env
/actuator/configprops

生产上应关闭或严格保护这些端点。

常见问题:

问题 原因
配置未生效 优先级低、缩进错误、Profile 未激活
类型绑定失败 格式错误、单位缺失
环境变量没绑定 命名不符合松绑定规则
密码泄露 env 端点或日志输出
刷新无效 Bean 不在刷新范围或引用未更新

本章小结

Spring Environment 把命令行、系统属性、环境变量、Profile 文件和配置中心统一成有序 PropertySource。业务配置应通过 @ConfigurationProperties 集中绑定和校验,敏感信息交给密钥系统。多环境使用 Profile,业务开关使用显式配置和动态刷新机制,并用安全手段控制配置可见性。

思考题

  1. PropertySource 的顺序为什么重要?
  2. @Value@ConfigurationProperties 各适合什么场景?
  3. 环境变量如何映射到嵌套配置?
  4. @RefreshScope 有哪些副作用?
  5. 如何保证生产配置缺失时应用无法启动?