11. 配置管理

4 阅读5分钟

一、配置管理概述

1. 为什么需要配置管理?

问题影响
硬编码配置修改配置需要重新编译部署
环境差异开发/测试/生产环境配置混乱
敏感信息泄露密码、密钥暴露在代码中
配置分散难以统一管理和维护

2. 配置管理的好处

好处说明
环境隔离不同环境使用不同配置
外部化配置配置与代码分离
热更新无需重启即可更新配置
版本控制配置文件可以纳入版本管理

二、配置文件

1. 配置文件优先级

Spring Boot 按照以下顺序加载配置文件(优先级从高到低):

优先级位置说明
1命令行参数--server.port=8081
2环境变量SERVER_PORT=8081
3外部配置文件./config/application.properties
4外部配置文件./application.properties
5内部配置文件classpath:/config/application.properties
6内部配置文件classpath:/application.properties
7默认配置Spring Boot 默认配置

2. 配置文件格式

application.properties

# 服务器配置
server.port=8080
server.servlet.context-path=/api

# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=root

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

# 应用配置
app.name=My Application
app.version=1.0.0

application.yml

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: root
  
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

app:
  name: My Application
  version: 1.0.0

application.yaml

# application.yaml 与 application.yml 功能相同
# yaml 和 yml 是同一格式的不同扩展名
server:
  port: 8080

3. 文件格式对比

特性.properties.yml/.yaml
层级关系使用点号分隔使用缩进表示
可读性较差较好
表达式支持 SpEL支持 SpEL
复杂结构需要索引原生支持
推荐场景简单配置复杂配置

三、环境配置

1. Profile 机制

创建不同环境的配置文件

src/main/resources/
├── application.properties           # 默认配置
├── application-dev.properties       # 开发环境
├── application-test.properties      # 测试环境
└── application-prod.properties      # 生产环境

application-dev.properties

# 开发环境配置
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/dev_db
spring.datasource.username=root
spring.datasource.password=dev_pass

logging.level.com.example.myapp=DEBUG

application-test.properties

# 测试环境配置
server.port=8081

spring.datasource.url=jdbc:mysql://localhost:3306/test_db
spring.datasource.username=root
spring.datasource.password=test_pass

logging.level.com.example.myapp=INFO

application-prod.properties

# 生产环境配置
server.port=443

spring.datasource.url=jdbc:mysql://prod-server:3306/prod_db
spring.datasource.username=prod_user
spring.datasource.password=${DB_PASSWORD}

logging.level.com.example.myapp=WARN
logging.file.name=/var/log/myapp/app.log

2. 激活 Profile

方式一:配置文件

# application.properties
spring.profiles.active=dev

方式二:命令行参数

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

方式三:环境变量

export SPRING_PROFILES_ACTIVE=prod
java -jar myapp.jar

方式四:启动类

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);
        app.setAdditionalProfiles("dev");  // 设置激活的 Profile
        app.run(args);
    }
}

3. 配置文件继承

application.properties(基础配置)
    ↓
application-{profile}.properties(环境特定配置)
    ↓
最终配置(合并)

示例:

application.properties

app.name=My Application
app.version=1.0.0

application-dev.properties

app.environment=Development
logging.level=DEBUG

最终配置(dev 环境):

app.name=My Application
app.version=1.0.0
app.environment=Development
logging.level=DEBUG

四、配置读取

1. @Value 注解

基本用法

@Component
public class MyComponent {
    
    @Value("${server.port}")
    private int serverPort;
    
    @Value("${app.name}")
    private String appName;
    
    @Value("${app.version}")
    private String appVersion;
    
    public void printConfig() {
        System.out.println("Server Port: " + serverPort);
        System.out.println("App Name: " + appName);
        System.out.println("App Version: " + appVersion);
    }
}

默认值

@Component
public class MyComponent {
    
    @Value("${app.timeout:3000}")  // 默认值 3000
    private int timeout;
    
    @Value("${app.description:无描述}")  // 默认值 "无描述"
    private String description;
    
    @Value("${app.enabled:true}")  // 默认值 true
    private boolean enabled;
}

SpEL 表达式

@Component
public class MyComponent {
    
    // 计算表达式
    @Value("#{10 * 20}")
    private int result;
    
    // 读取系统属性
    @Value("#{systemProperties['user.home']}")
    private String userHome;
    
    // 读取环境变量
    @Value("#{systemEnvironment['JAVA_HOME']}")
    private String javaHome;
    
    // 条件判断
    @Value("#{app.timeout > 5000 ? 'Long' : 'Short'}")
    private String timeoutType;
}

2. @ConfigurationProperties

创建配置类

package com.example.myapp.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    
    private String name;
    private String version;
    private String description;
    
    private Database database = new Database();
    private Cache cache = new Cache();
    private Feature feature = new Feature();
    
    @Data
    public static class Database {
        private String url;
        private String username;
        private String password;
        private int maxConnections = 10;
    }
    
    @Data
    public static class Cache {
        private boolean enabled = true;
        private String type = "redis";
        private long ttl = 3600;
    }
    
    @Data
    public static class Feature {
        private boolean newUserRegistration = true;
        private boolean emailVerification = false;
        private boolean socialLogin = true;
    }
}

配置文件

application.yml

app:
  name: My Application
  version: 1.0.0
  description: Spring Boot Demo Application
  
  database:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: root
    max-connections: 20
  
  cache:
    enabled: true
    type: redis
    ttl: 7200
  
  feature:
    new-user-registration: true
    email-verification: true
    social-login: false

使用配置

@Service
public class MyService {
    
    @Autowired
    private AppProperties appProperties;
    
    public void printConfig() {
        System.out.println("App Name: " + appProperties.getName());
        System.out.println("Version: " + appProperties.getVersion());
        System.out.println("DB URL: " + appProperties.getDatabase().getUrl());
        System.out.println("Cache Enabled: " + appProperties.getCache().isEnabled());
        System.out.println("Features: " + appProperties.getFeature());
    }
}

3. 配置验证

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

验证配置

@Data
@Component
@ConfigurationProperties(prefix = "app")
@Validated  // 启用验证
public class AppProperties {
    
    @NotBlank(message = "应用名称不能为空")
    private String name;
    
    @Pattern(regexp = "\\d+\\.\\d+\\.\\d+", message = "版本号格式不正确")
    private String version;
    
    @Min(value = 1000, message = "超时时间不能小于 1000ms")
    private int timeout;
    
    @Valid
    private Database database = new Database();
    
    @Data
    public static class Database {
        @NotBlank(message = "数据库 URL 不能为空")
        private String url;
        
        @Min(value = 1, message = "最大连接数不能小于 1")
        @Max(value = 100, message = "最大连接数不能大于 100")
        private int maxConnections = 10;
    }
}

五、环境变量

1. 环境变量命名规则

配置属性环境变量
server.portSERVER_PORT
spring.datasource.urlSPRING_DATASOURCE_URL
app.database.usernameAPP_DATABASE_USERNAME

2. 使用环境变量

方式一:直接设置

export SERVER_PORT=8081
export SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/mydb
export SPRING_DATASOURCE_USERNAME=root
export SPRING_DATASOURCE_PASSWORD=root

java -jar myapp.jar

方式二:Docker

FROM openjdk:17-jdk-slim

ENV SERVER_PORT=8080
ENV SPRING_PROFILES_ACTIVE=prod
ENV DB_PASSWORD=secure_password

COPY target/myapp.jar /app/myapp.jar

EXPOSE 8080

CMD ["java", "-jar", "/app/myapp.jar"]

方式三:Docker Compose

version: '3.8'

services:
  myapp:
    image: myapp:latest
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=root
    depends_on:
      - mysql
  
  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=mydb

六、配置加密

1. Jasypt 加密

添加依赖

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>

配置 Jasypt

application.yml

jasypt:
  encryptor:
    password: my-secret-key  # 加密密钥(应该从环境变量获取)
    algorithm: PBEWithMD5AndDES
    iv-generator-classname: org.jasypt.iv.NoIvGenerator

spring:
  datasource:
    password: ENC(encrypted_password_here)

加密密码

import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;

public class PasswordEncryptor {
    public static void main(String[] args) {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setPassword("my-secret-key");  // 加密密钥
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        
        String plainPassword = "root";
        String encryptedPassword = encryptor.encrypt(plainPassword);
        
        System.out.println("Plain: " + plainPassword);
        System.out.println("Encrypted: " + encryptedPassword);
    }
}

从环境变量读取密钥

export JASYPT_ENCRYPTOR_PASSWORD=production-secret-key
java -jar myapp.jar

2. 云配置中心

Spring Cloud Config

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

配置服务端:

@SpringBootApplication
@EnableConfigServer  // 启用配置服务器
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

配置文件:

server:
  port: 8888

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-repo/config-repo
          default-label: main

客户端配置:

spring:
  cloud:
    config:
      uri: http://localhost:8888
      name: myapp
      profile: prod

七、配置刷新

1. Actuator 配置刷新

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启用刷新端点

application.yml

management:
  endpoints:
    web:
      exposure:
        include: refresh,health,info

标记配置为可刷新

@Component
@RefreshScope  // 标记为可刷新
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    // 配置属性
}

刷新配置

curl -X POST http://localhost:8080/actuator/refresh

2. 配置监听

@Component
public class ConfigChangeListener {
    
    @EventListener
    public void onEnvironmentChangeEvent(
            EnvironmentChangeEvent event
    ) {
        System.out.println("配置已更新:");
        event.getKeys().forEach(key -> {
            System.out.println("  - " + key);
        });
    }
}

八、最佳实践

1. 配置管理最佳实践

实践说明
外部化配置配置与代码分离
环境隔离使用 Profile 区分环境
敏感信息加密使用 Jasypt 或密钥管理服务
使用配置类@ConfigurationProperties 优于 @Value
配置验证使用 @Validated 验证配置
文档化配置为配置提供说明文档

2. 安全性最佳实践

实践说明
不在代码中硬编码密码使用环境变量或密钥管理
加密敏感信息使用 Jasypt 或云密钥服务
限制配置访问使用安全配置中心
定期轮换密钥定期更新加密密钥
最小权限原则应用只访问必要的配置

九、总结

概念说明
配置文件properties 和 yml 格式
Profile环境隔离机制
@Value简单配置注入
@ConfigurationProperties类型安全配置绑定
环境变量外部配置源
配置加密Jasypt 加密敏感信息
配置刷新热更新配置