Skip to content

配置管理

LCGYL Framework 提供了灵活强大的配置管理系统,支持多种配置源、类型转换、热更新等特性。

核心概念

配置源(PropertySource)

配置源是配置数据的来源,框架支持多种配置源:

  • 文件配置:properties、yaml、json
  • 环境变量:系统环境变量
  • 命令行参数:启动参数
  • 远程配置:配置中心(如 Nacos、Apollo)

配置优先级

配置源按照优先级加载,高优先级覆盖低优先级:

命令行参数 > 环境变量 > 配置文件 > 默认值

基础使用

1. 创建配置文件

properties
# application.properties

# 应用配置
app.name=LCGYL Application
app.version=1.0.0
app.description=A powerful framework

# 服务器配置
server.host=localhost
server.port=8080
server.contextPath=/api

# 数据库配置
database.url=jdbc:mysql://localhost:3306/lcgyl
database.username=root
database.password=123456
database.pool.maxSize=20
database.pool.minSize=5

# 日志配置
logging.level=INFO
logging.file=/var/log/app.log

2. 加载配置

java
import com.lcgyl.core.config.Configuration;
import com.lcgyl.core.config.ConfigurationManager;

public class ConfigExample {
    
    public static void main(String[] args) {
        // 加载配置
        Configuration config = ConfigurationManager.load("application.properties");
        
        // 读取配置
        String appName = config.getString("app.name");
        int port = config.getInt("server.port");
        boolean debug = config.getBoolean("app.debug", false); // 带默认值
        
        System.out.println("应用名称: " + appName);
        System.out.println("端口: " + port);
        System.out.println("调试模式: " + debug);
    }
}

3. 类型转换

java
// 基本类型
String name = config.getString("app.name");
int port = config.getInt("server.port");
long timeout = config.getLong("timeout");
double rate = config.getDouble("rate");
boolean enabled = config.getBoolean("enabled");

// 集合类型
List<String> hosts = config.getList("hosts", String.class);
Set<Integer> ports = config.getSet("ports", Integer.class);
Map<String, String> headers = config.getMap("headers");

// 自定义类型
Duration timeout = config.get("timeout", Duration.class);
LocalDateTime time = config.get("time", LocalDateTime.class);

配置绑定

1. 配置类

java
import com.lcgyl.core.config.ConfigurationProperties;

@ConfigurationProperties(prefix = "database")
public class DatabaseConfig {
    
    private String url;
    private String username;
    private String password;
    private PoolConfig pool;
    
    // Getters and Setters
    
    public static class PoolConfig {
        private int maxSize = 20;
        private int minSize = 5;
        private long maxWait = 30000;
        
        // Getters and Setters
    }
}

2. 绑定配置

java
// 自动绑定
DatabaseConfig dbConfig = ConfigurationManager.bind(
    DatabaseConfig.class,
    config
);

// 使用配置
System.out.println("数据库URL: " + dbConfig.getUrl());
System.out.println("连接池最大连接数: " + dbConfig.getPool().getMaxSize());

3. 嵌套配置

java
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    
    private String name;
    private String version;
    private ServerConfig server;
    private SecurityConfig security;
    
    public static class ServerConfig {
        private String host;
        private int port;
        private String contextPath;
    }
    
    public static class SecurityConfig {
        private boolean enabled;
        private String secretKey;
        private List<String> allowedOrigins;
    }
}

多环境配置

1. 环境配置文件

config/
├── application.properties          # 通用配置
├── application-dev.properties      # 开发环境
├── application-test.properties     # 测试环境
└── application-prod.properties     # 生产环境

2. 激活环境

java
// 方式1:代码指定
ConfigurationManager.setActiveProfile("prod");

// 方式2:环境变量
// export LCGYL_PROFILE=prod

// 方式3:命令行参数
// java -jar app.jar --profile=prod

3. 环境特定配置

properties
# application-prod.properties
database.url=jdbc:mysql://prod-db:3306/lcgyl
database.pool.maxSize=50
logging.level=WARN

配置源管理

1. 多配置源

java
import com.lcgyl.core.config.PropertySource;

public class MultiSourceExample {
    
    public static void main(String[] args) {
        // 创建配置管理器
        ConfigurationManager manager = new ConfigurationManager();
        
        // 添加配置源(按优先级)
        manager.addPropertySource(
            PropertySource.fromCommandLine(args),        // 优先级 1
            PropertySource.fromEnvironment(),            // 优先级 2
            PropertySource.fromFile("application.properties"), // 优先级 3
            PropertySource.fromDefaults()                // 优先级 4
        );
        
        // 获取配置
        Configuration config = manager.getConfiguration();
    }
}

2. 自定义配置源

java
public class DatabasePropertySource implements PropertySource {
    
    private final DataSource dataSource;
    
    public DatabasePropertySource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Override
    public String get(String key) {
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(
                 "SELECT value FROM config WHERE key = ?")) {
            
            stmt.setString(1, key);
            ResultSet rs = stmt.executeQuery();
            
            return rs.next() ? rs.getString("value") : null;
        } catch (SQLException e) {
            throw new ConfigurationException("Failed to load config", e);
        }
    }
    
    @Override
    public Map<String, String> getAll() {
        // 实现获取所有配置
    }
}

// 使用
manager.addPropertySource(new DatabasePropertySource(dataSource));

3. 远程配置中心

java
public class NacosPropertySource implements PropertySource {
    
    private final ConfigService configService;
    private final String dataId;
    private final String group;
    
    public NacosPropertySource(String serverAddr, String dataId, String group) {
        try {
            Properties properties = new Properties();
            properties.put("serverAddr", serverAddr);
            this.configService = NacosFactory.createConfigService(properties);
            this.dataId = dataId;
            this.group = group;
        } catch (NacosException e) {
            throw new ConfigurationException("Failed to connect to Nacos", e);
        }
    }
    
    @Override
    public String get(String key) {
        try {
            String content = configService.getConfig(dataId, group, 5000);
            Properties props = new Properties();
            props.load(new StringReader(content));
            return props.getProperty(key);
        } catch (Exception e) {
            throw new ConfigurationException("Failed to get config", e);
        }
    }
}

配置热更新

1. 监听配置变化

java
import com.lcgyl.core.config.ConfigurationListener;

public class ConfigChangeExample {
    
    public static void main(String[] args) {
        Configuration config = ConfigurationManager.load("application.properties");
        
        // 添加监听器
        config.addListener(new ConfigurationListener() {
            @Override
            public void onConfigChanged(String key, String oldValue, String newValue) {
                System.out.printf("配置变更: %s = %s -> %s%n", 
                    key, oldValue, newValue);
                
                // 处理配置变更
                if ("server.port".equals(key)) {
                    restartServer(Integer.parseInt(newValue));
                }
            }
        });
        
        // 启用热更新
        config.enableHotReload(5000); // 每 5 秒检查一次
    }
}

2. 配置刷新

java
public class RefreshableConfig {
    
    private final Configuration config;
    private volatile int maxConnections;
    
    public RefreshableConfig(Configuration config) {
        this.config = config;
        this.maxConnections = config.getInt("pool.maxConnections");
        
        // 监听配置变化
        config.addListener((key, oldValue, newValue) -> {
            if ("pool.maxConnections".equals(key)) {
                this.maxConnections = Integer.parseInt(newValue);
                System.out.println("连接池大小已更新: " + maxConnections);
            }
        });
    }
    
    public int getMaxConnections() {
        return maxConnections;
    }
}

配置验证

1. 基本验证

java
@ConfigurationProperties(prefix = "server")
public class ServerConfig {
    
    @NotNull
    @Min(1024)
    @Max(65535)
    private Integer port;
    
    @NotBlank
    @Pattern(regexp = "^[a-zA-Z0-9.-]+$")
    private String host;
    
    @Positive
    private int threadPoolSize;
    
    // Getters and Setters
}

// 验证配置
ServerConfig config = ConfigurationManager.bind(ServerConfig.class);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<ServerConfig>> violations = validator.validate(config);

if (!violations.isEmpty()) {
    violations.forEach(v -> 
        System.err.println(v.getPropertyPath() + ": " + v.getMessage())
    );
}

2. 自定义验证

java
public class ConfigValidator {
    
    public static void validate(Configuration config) {
        // 验证必需配置
        requireNonNull(config, "app.name", "应用名称不能为空");
        requireNonNull(config, "server.port", "服务端口不能为空");
        
        // 验证端口范围
        int port = config.getInt("server.port");
        if (port < 1024 || port > 65535) {
            throw new ConfigurationException("端口必须在 1024-65535 之间");
        }
        
        // 验证数据库URL
        String dbUrl = config.getString("database.url");
        if (!dbUrl.startsWith("jdbc:")) {
            throw new ConfigurationException("无效的数据库URL");
        }
    }
    
    private static void requireNonNull(Configuration config, String key, String message) {
        if (!config.contains(key)) {
            throw new ConfigurationException(message);
        }
    }
}

配置加密

1. 加密敏感配置

java
import com.lcgyl.core.config.EncryptionSupport;

public class EncryptedConfigExample {
    
    public static void main(String[] args) {
        // 创建加密支持
        EncryptionSupport encryption = new EncryptionSupport("my-secret-key");
        
        // 加密配置
        String password = "myPassword123";
        String encrypted = encryption.encrypt(password);
        
        // 保存到配置文件
        // database.password=ENC(encrypted-value)
        
        // 读取时自动解密
        Configuration config = ConfigurationManager.load("application.properties");
        config.setEncryptionSupport(encryption);
        
        String decrypted = config.getString("database.password");
        System.out.println("解密后的密码: " + decrypted);
    }
}

2. 配置文件示例

properties
# application.properties

# 明文配置
app.name=LCGYL Application

# 加密配置(使用 ENC() 包裹)
database.password=ENC(AES256:base64-encrypted-value)
api.secretKey=ENC(AES256:another-encrypted-value)

实战示例

完整的配置管理

java
public class ApplicationConfig {
    
    private static final Logger logger = LoggerFactory.getLogger(ApplicationConfig.class);
    
    public static Configuration loadConfiguration(String[] args) {
        // 1. 创建配置管理器
        ConfigurationManager manager = new ConfigurationManager();
        
        // 2. 确定环境
        String profile = determineProfile(args);
        logger.info("激活环境: {}", profile);
        
        // 3. 添加配置源(按优先级)
        manager.addPropertySource(
            // 命令行参数(最高优先级)
            PropertySource.fromCommandLine(args),
            
            // 环境变量
            PropertySource.fromEnvironment(),
            
            // 环境特定配置文件
            PropertySource.fromFile("application-" + profile + ".properties"),
            
            // 通用配置文件
            PropertySource.fromFile("application.properties"),
            
            // 默认配置(最低优先级)
            PropertySource.fromDefaults()
        );
        
        // 4. 获取配置
        Configuration config = manager.getConfiguration();
        
        // 5. 配置加密支持
        String encryptionKey = System.getenv("CONFIG_ENCRYPTION_KEY");
        if (encryptionKey != null) {
            config.setEncryptionSupport(new EncryptionSupport(encryptionKey));
        }
        
        // 6. 验证配置
        ConfigValidator.validate(config);
        
        // 7. 启用热更新(生产环境)
        if ("prod".equals(profile)) {
            config.enableHotReload(60000); // 每分钟检查一次
        }
        
        // 8. 添加配置监听器
        config.addListener((key, oldValue, newValue) -> {
            logger.info("配置变更: {} = {} -> {}", key, oldValue, newValue);
        });
        
        return config;
    }
    
    private static String determineProfile(String[] args) {
        // 1. 检查命令行参数
        for (String arg : args) {
            if (arg.startsWith("--profile=")) {
                return arg.substring("--profile=".length());
            }
        }
        
        // 2. 检查环境变量
        String profile = System.getenv("LCGYL_PROFILE");
        if (profile != null) {
            return profile;
        }
        
        // 3. 默认开发环境
        return "dev";
    }
}

最佳实践

1. 配置组织

properties
# ✅ 好的实践:使用分层命名
app.name=MyApp
app.version=1.0.0

server.host=localhost
server.port=8080
server.ssl.enabled=true
server.ssl.keyStore=/path/to/keystore

database.primary.url=jdbc:mysql://localhost/db1
database.primary.username=user1
database.secondary.url=jdbc:mysql://localhost/db2
database.secondary.username=user2

# ❌ 不好的实践:扁平命名
appName=MyApp
serverHost=localhost
databaseUrl=jdbc:mysql://localhost/db

2. 默认值

java
// ✅ 好的实践:提供合理的默认值
int port = config.getInt("server.port", 8080);
int threadPoolSize = config.getInt("thread.pool.size", 
    Runtime.getRuntime().availableProcessors() * 2);

// ❌ 不好的实践:硬编码
int port = 8080; // 无法配置

3. 配置文档化

properties
# 服务器配置
# server.host: 服务器主机地址(默认:localhost)
# server.port: 服务器端口(默认:8080,范围:1024-65535)
# server.contextPath: 应用上下文路径(默认:/)
server.host=localhost
server.port=8080
server.contextPath=/api

4. 敏感信息保护

properties
# ✅ 好的实践:加密敏感信息
database.password=ENC(encrypted-value)
api.secretKey=ENC(encrypted-value)

# ❌ 不好的实践:明文存储
database.password=myPassword123
api.secretKey=abc123def456

注意事项

  1. 配置优先级:理解配置源的优先级,避免配置被意外覆盖
  2. 类型安全:使用配置类绑定,而不是字符串键值
  3. 验证配置:启动时验证配置的完整性和正确性
  4. 敏感信息:加密存储密码、密钥等敏感信息
  5. 环境隔离:不同环境使用不同的配置文件
  6. 热更新谨慎:生产环境的热更新要经过充分测试

下一步

Released under the Apache License 2.0