1. 概述

在应用部署时,我们通常需要从多种外部源提供配置信息。Apache Commons Configuration 提供了统一管理不同来源配置的解决方案。

本文将探索如何利用 Apache Commons Configuration 高效配置应用,涵盖从基础使用到高级特性的完整实践。

2. Apache Commons Configuration 简介

该库为 Java 应用提供了访问多样化配置数据的统一接口。通过配置构建器,它支持对单值和多值特性的类型化访问

它能一致处理来自文件、数据库和 XML 等分层文档的配置属性。

2.1. Maven 依赖

首先在 pom.xml 中添加最新版本的依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-configuration2</artifactId>
    <version>2.10.0</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

2.2. 环境准备

创建两种常见配置文件示例:

平铺格式(*.properties*):

db.host=baeldung.com
db.port=9999
db.user=admin
db.password=bXlTZWNyZXRTdHJpbmc=
db.url=${db.host}:${db.port}
db.username=${sys:user.name}
db.external-service=${const:com.baeldung.commons.configuration.ExternalServices.BAELDUNG_WEBSITE}

分层 XML 格式

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration SYSTEM "validation-sample.dtd">
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>Pattern1</pattern>
            <pattern>Pattern2</pattern>
        </encoder>
    </appender>

    <root>
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

3. Configurations 工具类

Configurations 工具类提供线程安全的快速启动方案,支持从不同源读取配置。可通过 Parameters 实例自定义参数。

3.1. 读取 Properties 文件

通过 Configurations 读取并访问配置:

Configurations configs = new Configurations();
Configuration config = configs.properties(new File("src/test/resources/configuration/file.properties"));
String dbHost = config.getString("db.host");
int dbPort = config.getInt("db.port");
String dbUser = config.getString("db.user");
String dbPassword = config.getString("undefinedKey", "defaultValue");

assertEquals("baeldung.com", dbHost);
assertEquals(9999, dbPort);
assertEquals("admin", dbUser);
assertEquals("defaultValue", dbPassword);

✅ 支持类型转换(数字、List等)
✅ 可指定默认值避免异常

3.2. 读取 XML 文件

使用 XMLConfiguration 访问分层结构:

Configurations configs = new Configurations();
XMLConfiguration config = configs.xml(new File("src/test/resources/configuration/hierarchical.xml"));
String appender = config.getString("appender[@name]");
List<String> encoderPatterns = config.getList(String.class, "appender.encoder.pattern");
String pattern1 = config.getString("appender.encoder.pattern(0)");

通过点号(.)表示法遍历分层元素。

4. 基于 Properties 的配置

除工具类外,还支持通过 FileBasedConfigurationBuilder 创建配置对象:

Parameters params = new Parameters();
FileBasedConfigurationBuilder<FileBasedConfiguration> builder = 
  new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
    .configure(params.properties()
    .setFileName("src/test/resources/configuration/file1.properties"));

⚠️ 高级特性

  • 通过 includeincludeOptional 关联其他配置文件
    db.host=baeldung.com
    include=file.properties
    includeOptional=file2.properties  // 文件不存在时不会报错
    
  • 可自定义 IO 操作(继承 PropertiesReader/Writer

5. 基于 XML 的配置

XMLConfiguration 支持 XML 配置解析,提供两种验证机制

  1. validating:启用基础解析验证
  2. schemaValidation:启用 Schema 验证

示例 Schema

<!ELEMENT configuration (appender+, root)>
<!ELEMENT appender (encoder?)>
<!ATTLIST appender
    name CDATA #REQUIRED
    class CDATA #REQUIRED
    >
<!ELEMENT encoder (pattern+)>
<!ELEMENT pattern (#PCDATA)>
<!ELEMENT root (appender-ref+)>
<!ELEMENT appender-ref EMPTY>
<!ATTLIST appender-ref
    ref CDATA #REQUIRED
    >

验证代码

Parameters params = new Parameters();
FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
  .configure(params.xml()
  .setFileName("src/test/resources/configuration/hierarchical.xml")
  .setValidating(true));
XMLConfiguration config = builder.getConfiguration();
String appender = config.getString("appender[@name]");
List<String> encoderPatterns = config.getList(String.class, "appender.encoder.pattern");

assertEquals("STDOUT", appender);
assertEquals(2, encoderPatterns.size());

6. 多租户配置

通过 MultiFileConfigurationBuilder 支持多租户场景:

System.setProperty("tenant", "A");
String filePattern = "src/test/resources/configuration/tenant-${sys:tenant}.properties";
MultiFileConfigurationBuilder<PropertiesConfiguration> builder = new MultiFileConfigurationBuilder<>(
  PropertiesConfiguration.class)
    .configure(new Parameters()
      .multiFile()
      .setFilePattern(filePattern)
      .setPrefixLookups(ConfigurationInterpolator.getDefaultPrefixLookups()));
Configuration config = builder.getConfiguration();
String tenantAName = config.getString("name");

assertEquals("Tenant A", tenantAName);

关键点

  • 文件路径包含动态参数(如 ${sys:tenant}
  • 通过 DefaultPrefixLookups 解析变量

7. 数据类型处理

7.1. 缺失属性处理

PropertiesConfiguration propertiesConfig = new PropertiesConfiguration();
String objectProperty = propertiesConfig.getString("anyProperty"); // 返回 null
int primitiveProperty = propertiesConfig.getInt("anyProperty", 1); // 返回默认值

assertNull(objectProperty);
assertEquals(1, primitiveProperty);

注意

  • 对象类型返回 null
  • 基本类型抛出 NoSuchElementException(除非提供默认值)

7.2. 列表与数组处理

通过分隔符处理多值属性:

PropertiesConfiguration propertiesConfig = new PropertiesConfiguration();
propertiesConfig.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
propertiesConfig.addProperty("delimitedProperty", "admin;read-only;read-write");
propertiesConfig.addProperty("arrayProperty", "value1;value2");
List<Object> delimitedProperties = propertiesConfig.getList("delimitedProperty");
String[] arrayProperties = propertiesConfig.getStringArray("arrayProperty");

⚠️ 直接获取字符串时返回第一个值:

assertEquals("value1", propertiesConfig.getString("arrayProperty"));

8. 插值与表达式

8.1. 变量插值

支持在配置中使用占位符:

System.setProperty("user.name", "Baeldung");
String dbUrl = config.getString("db.url");       // 解析 ${db.host}:${db.port}
String userName = config.getString("db.username"); // 解析 ${sys:user.name}
String externalService = config.getString("db.external-service"); // 解析常量

支持的变量类型

  • 系统属性:${sys:...}
  • 环境变量:${env:...}
  • 类常量:${const:...}

8.2. 表达式求值

通过 JEXL 库支持表达式计算:

添加依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl</artifactId>
    <version>2.1.1</version>
</dependency>

配置示例

db.data-dump-location=${expr:System.getProperty("user.home")}/dump.dat

解析代码

System.setProperty("user.home", "/usr/lib");
Map<String, Lookup> lookups = new HashMap<>(ConfigurationInterpolator.getDefaultPrefixLookups());
ExprLookup.Variables variables = new ExprLookup.Variables();
variables.add(new ExprLookup.Variable("System", "Class:java.lang.System"));
ExprLookup exprLookup = new ExprLookup(variables);
exprLookup.setInterpolator(new ConfigurationInterpolator());
lookups.put("expr", exprLookup);

FileBasedConfigurationBuilder<FileBasedConfiguration> builder = 
  new FileBasedConfigurationBuilder<FileBasedConfiguration>(
    PropertiesConfiguration.class).configure(params.properties()
      .setFileName("src/test/resources/configuration/file1.properties")
      .setPrefixLookups(lookups));

9. 类型转换与解码

9.1. 自动类型转换

config.addProperty("stringProperty", "This is a string");
config.addProperty("numericProperty", "9999");
config.addProperty("booleanProperty", "true");
assertEquals("This is a string", config.getString("stringProperty"));
assertEquals(9999, config.getInt("numericProperty"));
assertTrue(config.getBoolean("booleanProperty"));

❌ 转换失败抛出 ConversionException

config.addProperty("numericProperty", "9999a");
assertThrows(ConversionException.class,()->config.getInt("numericProperty"));

9.2. 属性解码

实现 ConfigurationDecoder 接口处理加密属性:

public class CustomDecoder implements ConfigurationDecoder {
    @Override
    public String decode(String encodedValue) {
        return new String(Base64.decodeBase64(encodedValue));
    }
}

使用解码器

((AbstractConfiguration) config).setConfigurationDecoder(new CustomDecoder());
assertEquals("mySecretString", config.getEncodedString("db.password"));

10. 配置复制

10.1. copy() 方法

覆盖式复制

Configuration baseConfig = configs.properties(new File("src/test/resources/configuration/file.properties"));
Configuration subConfig = new PropertiesConfiguration();
subConfig.addProperty("db.host","baeldung");
((AbstractConfiguration) subConfig).copy(baseConfig);
String dbHost = subConfig.getString("db.host");
assertEquals("baeldung.com", dbHost); // 被覆盖

10.2. append() 方法

追加式复制

Configuration baseConfig = configs.properties(new File("src/test/resources/configuration/file.properties"));
Configuration subConfig = new PropertiesConfiguration();
subConfig.addProperty("db.host","baeldung");
((AbstractConfiguration) subConfig).append(baseConfig);
String dbHost = subConfig.getString("db.host");
assertEquals("baeldung", dbHost); // 保留原值

10.3. 分层配置复制

推荐方式(保持分层结构):

XMLConfiguration baseConfig = configs.xml(new File("src/test/resources/configuration/hierarchical.xml"));
XMLConfiguration subConfig = new XMLConfiguration();
subConfig = (XMLConfiguration) baseConfig.clone(); // 或 new XMLConfiguration(baseConfig)

⚠️ 普通的 copy()/append() 会破坏分层结构

11. 总结

本文系统介绍了 Apache Commons Configuration 的核心能力:

基础特性

  • 统一配置访问接口
  • 支持 Properties/XML 等多种格式
  • 类型安全的数据访问

高级特性

  • 多租户配置管理
  • 变量插值与表达式求值
  • 自动类型转换与属性解码
  • 配置合并与复制

适用场景

  • 需要统一管理多源配置的企业应用
  • 多租户 SaaS 系统
  • 复杂配置解析需求

完整代码示例见 GitHub 仓库


原始标题:Intro to Apache Commons Configuration Project