commons-configuration2(多数据源配置统一管理与热加载)
-
- 1、概述
- 2、构建器
-
- [2.1、核心类与 API 概览](#2.1、核心类与 API 概览)
- 2.2、构建器参数配置链 (Parameters)
- 2.3、使用示例
- 2.4、最佳实践
- 3、通用配置接口
- 4、结构化/树形配置
-
- 4.1、核心类与接口概览
- [4.2、表达式引擎语法(Node Keys)](#4.2、表达式引擎语法(Node Keys))
- [4.3、核心 API](#4.3、核心 API)
- 4.4、使用示例
- 4.5、最佳实践
- 5、复合与组合配置
- 6、参数构造器
1、概述
commons-configuration2(Apache Commons Configuration 2)是 Java 中功能极其强大的配置管理框架。它支持从 Properties 文件、XML、JSON、YAML、环境变量、系统属性、数据库等多种来源读取配置,并支持多配置文件合并、自动类型转换、动态变量插值(Variable Interpolation)、配置热加载(Auto-Reloading)等高级功能。
| 分类模块 | 常用类 / 接口 (Class/Interface) | 核心功能与解决问题 |
|---|---|---|
| 构建器 (Builder) | FileBasedConfigurationBuilder | 负责配置文件加载、解析、保存以及热加载监控 |
| 通用配置接口 (Config) | Configuration | 提供丰富的类型安全获取 API(如 getString, getInt, getBoolean) |
| 结构化/树形配置 | HierarchicalConfiguration | 专门用于读取 XML/JSON/YAML 等支持层级与节点嵌套的配置 |
| 复合与组合配置 | CombinedConfiguration | 将 Properties、XML、环境变量等多种配置源按优先级叠加合并 |
| 参数构造器 (Params) | Parameters, FileBasedBuilderParameters | 专门用于为 Builder 提供配置参数(文件名、编码、热刷新策略等) |
xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.10.1</version>
</dependency>
2、构建器
在 commons-configuration2 中,配置文件的加载、初始化与解析不再像 1.x 版本那样直接实例化 Configuration 类,而是全面引入了构建器模式(Builder Pattern)。
核心构建器接口是 ConfigurationBuilder(主要实现类为 FileBasedConfigurationBuilder)。构建器将配置的加载属性(如路径、编码、加载策略)与配置数据本身进行解耦,并提供了多线程安全访问和动态自动重载(Auto-Reloading)的能力。
2.1、核心类与 API 概览
| 类 / 接口 | 角色与职责 | 典型场景 |
|---|---|---|
| ConfigurationBuilder | 构建器核心接口 | 用于通过 getConfiguration() 获取配置对象实例 |
| FileBasedConfigurationBuilder | 基于文件的通用构建器 | 加载 Properties, XML, YAML, INI 等本地/远程配置文件 |
| Parameters | 参数生成器工厂 | 统一创建不同配置类型的构建参数对象(如 parameters.properties()) |
| BasicBuilderParameters | 基础构建参数类 | 设置通用参数(如并发策略、解析转换器等) |
| FileBasedBuilderParameters | 文件构建参数类 | 设置文件名、文件路径、URL、字符编码(如 UTF-8)等 |
| ReloadingFileBasedConfigurationBuilder | 动态重载构建器 | 监控配置文件变更,并在文件修改后自动重新加载 |
2.2、构建器参数配置链 (Parameters)
构建器采用链式调用的方式通过 Parameters 传入配置选项。针对不同的文件格式,Parameters 提供了对应的参数构建方法:
parameters.properties():配置 .properties 文件参数(可设置属性分隔符、注释字符等)。parameters.xml():配置 .xml 文件参数(可设置 Schema 校验、表达式引擎等)。parameters.fileBased():配置通用文件参数(设置路径 setFileName()、编码 setEncoding() 等)。
2.3、使用示例
1. 加载 Properties 配置文件(基础用法)
演示如何构建一个基础的 FileBasedConfigurationBuilder 并读取属性值。
java
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
public class PropertiesBuilderDemo {
public static void main(String[] args) {
// 1. 创建参数生成器工厂
Parameters params = new Parameters();
// 2. 初始化基于文件的配置构建器
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("config.properties") // 设置配置文件路径
.setEncoding("UTF-8") // 指定字符编码,解决中文乱码
.setThrowExceptionOnMissing(true) // 访问不存在的 key 时抛出异常
);
try {
// 3. 从构建器中获取真正的 Configuration 实例
PropertiesConfiguration config = builder.getConfiguration();
// 4. 读取配置数据
String siteName = config.getString("site.name", "默认站点");
int maxConnections = config.getInt("database.max-connections");
System.out.println("站点名称: " + siteName);
System.out.println("最大连接数: " + maxConnections);
} catch (ConfigurationException e) {
System.err.println("加载配置文件失败: " + e.getMessage());
}
}
}
2. 加载 XML 配置文件
XML 配置文件通常包含分层嵌套结构,构建器配置与 Properties 类似,但可以使用专门的 xml() 参数:
java
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
public class XmlBuilderDemo {
public static void main(String[] args) throws Exception {
Parameters params = new Parameters();
FileBasedConfigurationBuilder<XMLConfiguration> builder =
new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(params.xml()
.setFileName("app-config.xml")
.setValidating(false) // 是否开启 XML Schema 校验
.setEncoding("UTF-8")
);
XMLConfiguration config = builder.getConfiguration();
// 读取 XML 节点数据 (例如 <database><host>localhost</host></database>)
String dbHost = config.getString("database.host");
System.out.println("数据库 Host: " + dbHost);
}
}
3. 配置文件修改后的自动重载(Auto-Reloading)
在微服务或长期运行的后台应用中,我们希望在修改磁盘上的配置文件后,程序无需重启就能自动读取最新配置。ReloadingFileBasedConfigurationBuilder 配合 PeriodicReloadingTrigger 即可轻松实现。
java
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.builder.reloading.PeriodicReloadingTrigger;
import java.io.File;
import java.util.concurrent.TimeUnit;
public class AutoReloadingBuilderDemo {
public static void main(String[] args) throws Exception {
Parameters params = new Parameters();
File configFile = new File("dynamic-config.properties");
// 1. 创建支持自动重载的构建器
ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(params.fileBased()
.setFile(configFile)
.setEncoding("UTF-8")
);
// 2. 创建定时检测触发器 (每 2 秒检测一次文件修改时间)
PeriodicReloadingTrigger trigger = new PeriodicReloadingTrigger(
builder.getReloadingController(),
null,
2, TimeUnit.SECONDS
);
// 3. 启动定时检测线程
trigger.start();
System.out.println("开始监听配置文件修改...");
// 4. 模拟循环获取最新配置
for (int i = 0; i < 10; i++) {
// 每次调用 getConfiguration(),构建器会自动检测文件是否变更并重载
PropertiesConfiguration config = builder.getConfiguration();
System.out.println("[" + i + "] 当前配置 title: " + config.getString("app.title"));
Thread.sleep(3000); // 挂起等待,期间手动修改 dynamic-config.properties 文件即可看到控制台更新
}
// 5. 任务结束关闭触发器
trigger.stop();
}
}
4. 通过构建器保存/修改配置数据
构建器不仅可以加载配置,还可以方便地将更新后的数据保存回磁盘。
java
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import java.io.File;
public class SaveConfigurationDemo {
public static void main(String[] args) throws Exception {
File file = new File("user-settings.properties");
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(new Parameters().fileBased()
.setFile(file)
.setEncoding("UTF-8"));
PropertiesConfiguration config = builder.getConfiguration();
// 修改配置项
config.setProperty("user.theme", "dark");
config.setProperty("user.last_login", System.currentTimeMillis());
// 使用构建器将更改写回文件
builder.save();
System.out.println("配置更新并保存成功!");
}
}
2.4、最佳实践
- 单例模式持有 Builder 而非 Configuration:
在应用中,推荐将 ConfigurationBuilder 作为单例组件进行管理。当业务需要读取配置时,调用 builder.getConfiguration()。构建器内部有高效的缓存机制,只有在检测到重载事件时才会重新创建 Configuration 对象。 - 显式指定文件编码:
始终在 Parameters 中设置 .setEncoding("UTF-8"),避免在不同操作系统(Windows / Linux)下因默认字符集不同导致中文配置乱码。 - 异常处理机制:
通过设置 .setThrowExceptionOnMissing(true),可在配置项缺失时立即触发异常,防止因为默认返回 null 或 0 隐蔽地引入业务 Bug。
3、通用配置接口
在 commons-configuration2 中,Configuration 接口是所有配置对象的最高层通用抽象。无论底层数据源是 .properties、.xml、.json、环境变量、系统属性还是数据库,客户端都可以统一通过该接口进行操作,实现了配置源与业务逻辑的解耦。
核心接口继承体系
java
Configuration (核心通用接口)
├── ImmutableConfiguration (只读配置接口)
├── AbstractConfiguration (抽象实现,提供基础数据转换)
│ ├── BaseConfiguration (内存键值对配置类)
│ └── CompositeConfiguration (组合配置类)
└── HierarchicalConfiguration (分层/树状配置接口,如 XML/JSON)
└── XMLConfiguration
3.1、核心API
Configuration 接口提供了非常丰富的类型转换与属性操作 API。
1. 只读查询与类型转换 API(继承自 ImmutableConfiguration)
getString(String key) / getString(String key, String defaultValue):获取字符串类型配置,支持默认值。getInt(String key) / getInt(String key, int defaultValue):获取基本整型配置(自动类型转换)。getBoolean / getLong / getDouble / getBigDecimal:获取对应的强类型数值。getList(String key) / getStringArray(String key):将逗号分隔的值自动解析为 List<Object> 或 String\[\]。containsKey(String key):检查某个 Key 是否存在。isEmpty():判断配置是否为空。getKeys():获取包含所有配置 Key 的迭代器(Iterator<String>)。
2. 配置修改与管理 API
setProperty(String key, Object value):覆盖/重写指定 Key 的值。addProperty(String key, Object value):添加指定 Key 的值(如果 Key 已存在,通常会演变为列表/多值)。clearProperty(String key):删除指定的配置项。clear():清空所有配置项。subset(String prefix):提取子集。截取指定前缀开头的所有配置,返回一个新的 Configuration 对象(会自动剥离前缀)。
3.2、使用示例
1. 基础数据读取与默认值处理
演示如何通过通用 Configuration 接口安全提取不同类型的值。
java
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.Configuration;
import java.util.List;
public class ConfigurationBasicDemo {
public static void main(String[] args) {
// 创建一个内存通用的 BaseConfiguration 对象
Configuration config = new BaseConfiguration();
// 写入测试数据
config.setProperty("app.name", "MyApp");
config.setProperty("app.port", "8080");
config.setProperty("app.enabled", "true");
config.setProperty("app.servers", "192.168.1.1, 192.168.1.2, 192.168.1.3");
// 1. 获取基础类型 (自动转换类型)
String name = config.getString("app.name");
int port = config.getInt("app.port");
boolean enabled = config.getBoolean("app.enabled");
// 2. 使用默认值 (Key 不存在时返回默认值)
int timeout = config.getInt("app.timeout", 5000);
// 3. 读取列表与数组 (默认按逗号分割)
List<Object> serverList = config.getList("app.servers");
String[] serverArray = config.getStringArray("app.servers");
System.out.println("应用名称: " + name);
System.out.println("运行端口: " + port);
System.out.println("是否启用: " + enabled);
System.out.println("超时时间 (默认): " + timeout + " ms");
System.out.println("服务器节点数: " + serverList.size());
}
}
2. 配置子集剥离 (subset)
在模块化开发中,常用 subset() 提取某个特定前缀的配置项(如数据库配置 db.xxx),并传入具体的子模块:
java
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.Configuration;
import java.util.Iterator;
public class ConfigurationSubsetDemo {
public static void main(String[] args) {
Configuration config = new BaseConfiguration();
// 模拟多模块配置
config.setProperty("db.host", "localhost");
config.setProperty("db.port", "3306");
config.setProperty("db.user", "root");
config.setProperty("redis.host", "127.0.0.1");
// 1. 截取所有以 "db" 为前缀的配置项
Configuration dbConfig = config.subset("db");
// 2. 访问子配置时无需再带 "db." 前缀
System.out.println("DB Host: " + dbConfig.getString("host")); // 输出 localhost
System.out.println("DB Port: " + dbConfig.getInt("port")); // 输出 3306
// 3. 遍历子集的所有 Key
Iterator<String> keys = dbConfig.getKeys();
while (keys.hasNext()) {
String key = keys.next();
System.out.println("子配置项: " + key + " = " + dbConfig.getString(key));
}
}
}
3. 组合配置 (CompositeConfiguration)
CompositeConfiguration 是 Configuration 的重要扩展实现。它允许将多个配置源(如命令行参数 > 环境变量 > 外部配置文件 > 默认配置)组合在一起,并按照优先级顺序查找配置。
java
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.CompositeConfiguration;
import org.apache.commons.configuration2.Configuration;
public class CompositeConfigurationDemo {
public static void main(String[] args) {
// 1. 创建默认/低优先级配置源
Configuration defaultConfig = new BaseConfiguration();
defaultConfig.setProperty("server.port", "8080");
defaultConfig.setProperty("server.host", "0.0.0.0");
// 2. 创建自定义/高优先级配置源
Configuration customConfig = new BaseConfiguration();
customConfig.setProperty("server.port", "9090"); // 覆盖默认端口
// 3. 创建组合配置对象
CompositeConfiguration composite = new CompositeConfiguration();
// 先添加的配置源优先级最高!
composite.addConfiguration(customConfig);
composite.addConfiguration(defaultConfig);
// 4. 读取组合配置 (端口取高优先级的 9090,Host 取默认配置的 0.0.0.0)
System.out.println("生效 Port: " + composite.getInt("server.port")); // 9090
System.out.println("生效 Host: " + composite.getString("server.host")); // 0.0.0.0
}
}
4. setProperty 与 addProperty 的区别
java
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.Configuration;
public class PropertySetVsAddDemo {
public static void main(String[] args) {
Configuration config = new BaseConfiguration();
// 1. setProperty 会直接覆盖旧值
config.setProperty("key1", "valueA");
config.setProperty("key1", "valueB");
System.out.println("key1 最终值: " + config.getString("key1")); // valueB
// 2. addProperty 会保留旧值并追加,变为列表
config.addProperty("key2", "valueA");
config.addProperty("key2", "valueB");
System.out.println("key2 列表大小: " + config.getList("key2").size()); // 2
System.out.println("key2 列表内容: " + config.getList("key2")); // [valueA, valueB]
}
}
3.3、最佳实践
- 面向 Configuration 接口编程:
在业务方法或 Service 组件中,方法参数应当声明为 Configuration 或 ImmutableConfiguration 接口类型,避免绑定到具体的 PropertiesConfiguration 或 XMLConfiguration 类上。 - 只读保护与 ImmutableConfiguration:
如果将配置暴露给外部模块或插件,且不希望外部业务代码误修改配置,可使用 ConfigurationUtils.unmodifiableConfiguration(config) 将其包装为只读配置对象。 - 列表分隔符(List Delimiter)处理:
默认情况下,commons-configuration2 会自动拆分包含逗号 , 的字符串。如果属性值本身包含逗号(如正则表达式或复合字符串),需要在构建配置时设置自定义的 ListDelimiterHandler(如 DisabledListDelimiterHandler)。
4、结构化/树形配置
在 commons-configuration2 中,当处理 XML、JSON、YAML 或 INI 等具有层级、嵌套关系或多重属性的数据源时,扁平的 Configuration 接口已无法满足需求。
为此,框架提供了 HierarchicalConfiguration(树形/分层配置接口)。它通过路径表达式(Keys with delimiters)和节点操作(Nodes),支持对复杂嵌套数据结构的精确查询与动态修改。
4.1、核心类与接口概览
| 类 / 接口 | 角色与职责 | 典型场景 |
|---|---|---|
| HierarchicalConfiguration | 树形配置核心接口 | 定义分层数据的访问、节点选择与结构查询方法 |
| BaseHierarchicalConfiguration | 内存树形配置类 | 纯内存构建树形结构的通用实现 |
| XMLConfiguration | 基于 XML 的树形实现 | 支持 XML 节点、属性(Attribute)、属性列表的嵌套解析 |
| YAMLConfiguration | 基于 YAML 的树形实现 | 支持 YAML 对象、嵌套列表与 Map 结构的解析 |
| SubnodeConfiguration | 子树配置类 | 提取树形结构中的某个子节点树,作为独立的配置对象操作 |
| DefaultExpressionEngine | 表达式引擎 | 负责解析节点路径语法(如 database.connection(0).host) |
4.2、表达式引擎语法(Node Keys)
HierarchicalConfiguration 使用路径语法定位节点。默认使用点号 . 分隔层级,括号 (n) 表示同名节点的索引(从 0 开始),@ 表示属性。
- 简单嵌套:database.server.host
- 数组/同名列表索引:tables.table(0).name(访问第一个 <table> 的 name)
- 属性访问(以 XML 为例):database.server@type 或 tables.table(0)@id
- 通配符查询:tables.table.name(返回所有 table 下 name 的值列表)
4.3、核心 API
configurationAt(String key):将指定节点切分为一个独立的 SubnodeConfiguration(相当于提取整棵子树)。当对子树修改时,修改会实时同步反馈给主树。configurationsAt(String key):针对同名列表节点(如多个 <server>),返回一个 List<HierarchicalConfiguration> 列表,方便遍历。getMaxIndex(String key):获取指定同名节点的最大索引值(例如有 3 个 ,返回 2;若不存在返回 -1)。clearTree(String key):彻底删除指定的节点及其下所有的子节点树。setExpressionEngine(ExpressionEngine engine):更换路径解析引擎(如使用支持 XPath 的 XPathExpressionEngine)。
4.4、使用示例
1. XML 嵌套节点与属性操作
假设有如下 app-config.xml 文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<app-name>TreeDemoApp</app-name>
<database type="MySQL">
<host>127.0.0.1</host>
<port>3306</port>
</database>
<servers>
<server id="101">
<ip>192.168.1.10</ip>
</server>
<server id="102">
<ip>192.168.1.11</ip>
</server>
</servers>
</config>
java
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
public class XmlTreeConfigDemo {
public static void main(String[] args) throws Exception {
// 构建 XMLConfiguration 实例
FileBasedConfigurationBuilder<XMLConfiguration> builder =
new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(new Parameters().xml().setFileName("app-config.xml"));
XMLConfiguration config = builder.getConfiguration();
// 1. 读取常规嵌套节点
String host = config.getString("database.host");
int port = config.getInt("database.port");
// 2. 读取 XML 节点属性 (@ 符号)
String dbType = config.getString("database@type");
System.out.println("数据库: " + dbType + " -> " + host + ":" + port);
// 3. 使用索引访问列表节点
String firstServerIp = config.getString("servers.server(0).ip");
String secondServerId = config.getString("servers.server(1)@id");
System.out.println("Server 0 IP: " + firstServerIp);
System.out.println("Server 1 ID: " + secondServerId);
}
}
java
数据库: null -> 127.0.0.1:3306
Server 0 IP: 192.168.1.10
Server 1 ID: null
2. 子树提取 (configurationAt) 与多节点遍历 (configurationsAt)
当配置文件层级较深或包含数组列表时,通过全路径访问非常繁琐。使用子树和列表提取可以大大简化代码:
java
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import java.util.List;
public class SubnodeTraversalDemo {
public static void main(String[] args) throws Exception {
FileBasedConfigurationBuilder<XMLConfiguration> builder =
new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(new Parameters().xml().setFileName("app-config.xml"));
XMLConfiguration config = builder.getConfiguration();
// 1. 提取 database 子树 (切断深层路径)
HierarchicalConfiguration<XMLConfiguration> dbConfig = config.configurationAt("database");
System.out.println("--- 提取子树查询 ---");
System.out.println("Host: " + dbConfig.getString("host")); // 无需再加 "database." 前缀
System.out.println("Type: " + dbConfig.getString("@type"));
// 2. 修改子树的配置 (变更会直接作用于根配置 config)
dbConfig.setProperty("port", 3307);
System.out.println("主树更新后的 Port: " + config.getInt("database.port")); // 3307
// 3. 遍历 server 同名节点列表
System.out.println("\n--- 遍历列表节点 ---");
List<HierarchicalConfiguration<XMLConfiguration>> serverList = config.configurationsAt("servers.server");
for (HierarchicalConfiguration<XMLConfiguration> server : serverList) {
String id = server.getString("@id");
String ip = server.getString("ip");
System.out.println("Server 节点 [id=" + id + "] -> IP: " + ip);
}
}
}
3. 使用 XPath 表达式引擎 (XPathExpressionEngine)
对于极为复杂的 XML/结构化文档,默认的 . / () / @ 语法可能显得力不从心。commons-configuration2 允许直接将路径解析切换为真正的 XPath 语法:
java
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
public class XPathEngineDemo {
public static void main(String[] args) throws Exception {
FileBasedConfigurationBuilder<XMLConfiguration> builder =
new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(new Parameters().xml().setFileName("app-config.xml"));
XMLConfiguration config = builder.getConfiguration();
// 切换为 XPath 表达式引擎
config.setExpressionEngine(new XPathExpressionEngine());
// 使用标准的 XPath 语法查询 (注意: 语法与默认引擎不同)
String dbHost = config.getString("database/host");
String dbType = config.getString("database/@type");
// 条件过滤查询 (查找 id 为 102 的 server 的 ip)
String targetIp = config.getString("servers/server[@id='102']/ip");
System.out.println("XPath 查询 DB Host: " + dbHost);
System.out.println("XPath 条件查询 IP: " + targetIp); // 192.168.1.11
}
}
4. 纯内存构建/动态添加树形节点
演示如何在代码中动态组装一棵配置树:
java
import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
public class DynamicTreeBuilderDemo {
public static void main(String[] args) {
BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
// 动态添加层级节点
config.addProperty("services.microservice.name", "order-service");
config.addProperty("services.microservice.port", 8081);
// 继续添加同名节点 (自动生成列表结构)
config.addProperty("services.microservice(-1).name", "user-service");
config.addProperty("services.microservice(1).port", 8082);
System.out.println("服务 0: " + config.getString("services.microservice(0).name"));
System.out.println("服务 1: " + config.getString("services.microservice(1).name"));
}
}
4.5、最佳实践
- 优先使用 configurationsAt 替代循环索引:
通过 for (int i=0; i <= config.getMaxIndex("key"); i++) 循环拼接字符串访问节点性能较差且代码臃肿。使用 configurationsAt("key") 返回子配置列表不仅语义清晰,性能也更好。 - configurationAt 的视图一致性:
通过 configurationAt() 获取的 SubnodeConfiguration 并不是一份拷贝,而是主配置树的一个视图(View)。对子配置对象的修改会立即反映在主配置中,保存主配置时子配置的修改也会同步写入磁盘。 - 选择合适的表达式引擎:
- 默认引擎 DefaultExpressionEngine 简单高效,适合大多数属性文件、YAML 和简单 XML。
- 如果涉及跨节点属性关联查询、复杂过滤条件(如 @status='active'),建议开启 XPathExpressionEngine。
5、复合与组合配置
在 commons-configuration2 中,应用程序通常需要从多个来源加载配置(如:命令行参数 > 环境变量 > 外部配置文件 > 内置默认配置)。为了实现这种多源整合与优先级覆盖,框架提供了两大核心机制:
- CompositeConfiguration(组合配置):以编程式的方式,将多个 Configuration 对象手动叠加在一起,按添加顺序进行优先级检索。
- CombinedConfiguration(复合配置):基于树形结构(HierarchicalConfiguration)的高级配置容器,支持通过声明式的 CombinedConfigurationBuilder(解析 configuration.xml)将多种格式的配置文件(Properties, XML, JSON 等)统一组装,并提供强大的变量插值与合并符(Override / Merge)能力。
5.1、组合配置与复合配置对比
| 特性 | CompositeConfiguration (组合配置) | CombinedConfiguration (复合配置) |
|---|---|---|
| 结构类型 | 扁平(基于普通 Configuration 接口) | 树形(继承自 BaseHierarchicalConfiguration) |
| 主要适用场景 | 简单编程式的多源覆盖(如:内存配置覆盖文件配置) | 复杂的企业级应用,需整合 XML、YAML、系统属性等多源嵌套配置 |
| 构建方式 | 手动 addConfiguration(...) | 编程式添加或通过 CombinedConfigurationBuilder 声明式解析 |
| 冲突处理 | 覆盖模式(Override):优先从第一个包含该 Key 的配置源读取 | 支持 Override(覆盖) 与 Merge(合并同名节点) 两种结构化策略 |
| 子树提取 | 不支持树形路径操作 | 支持 configurationAt()、XPath 查询及多层嵌套提取 |
5.2、核心API
1. CompositeConfiguration API
addConfiguration(Configuration config):添加一个配置源。先添加的配置源优先级最高。addConfiguration(Configuration config, boolean inMemory):添加配置源,并可将其指定为"内存配置源"(通过 setProperty 修改值时,默认会写入指定的内存配置源)。getNumberOfConfigurations():获取当前组合中包含的配置源数量。
2. CombinedConfiguration API
addConfiguration(Configuration config, String name, String at):添加配置源。name:该配置源的唯一名称(后续可根据名称提取)。at:挂载前缀路径(例如指定 at="database",则该配置源中的 host 会被映射为 database.host)。
getNodeCombiner() / setNodeCombiner(NodeCombiner combiner):设置节点合并器。OverrideCombiner(默认):高优先级的配置源直接覆盖低优先级的同名节点。MergeCombiner:将多个配置源中的同名列表或嵌套节点进行合并。
5.3、使用示例
1. CompositeConfiguration 编程式多源覆盖示例
模拟经典的"命令行参数 > 外部文件配置 > 默认配置"的三层优先级覆盖。
java
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.CompositeConfiguration;
import org.apache.commons.configuration2.Configuration;
public class CompositeConfigDemo {
public static void main(String[] args) {
// 1. 默认配置(低优先级)
Configuration defaultConfig = new BaseConfiguration();
defaultConfig.setProperty("server.port", 8080);
defaultConfig.setProperty("server.host", "0.0.0.0");
defaultConfig.setProperty("app.name", "DefaultApp");
// 2. 文件配置(中优先级)
Configuration fileConfig = new BaseConfiguration();
fileConfig.setProperty("server.port", 9090); // 覆盖默认端口
// 3. 命令行/运行时配置(高优先级)
Configuration cliConfig = new BaseConfiguration();
cliConfig.setProperty("server.port", 7070); // 覆盖文件与默认端口
// 4. 创建组合配置对象
CompositeConfiguration composite = new CompositeConfiguration();
// 依次添加配置源(注意:先添加的优先级最高!)
composite.addConfiguration(cliConfig); // 优先级 1
composite.addConfiguration(fileConfig); // 优先级 2
composite.addConfiguration(defaultConfig); // 优先级 3
// 5. 校验结果
System.out.println("生效端口 (Take from CLI): " + composite.getInt("server.port")); // 7070
System.out.println("生效主机 (Take from Default): " + composite.getString("server.host")); // 0.0.0.0
System.out.println("包含的配置源总数: " + composite.getNumberOfConfigurations()); // 3
}
}
2. CombinedConfiguration 树形合并与路径挂载(at 属性)
演示如何将独立的配置源挂载到指定的树形节点下,并设置重叠覆盖策略。
java
import org.apache.commons.configuration2.BaseConfiguration;
import org.apache.commons.configuration2.CombinedConfiguration;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.tree.OverrideCombiner;
public class CombinedConfigDemo {
public static void main(String[] args) {
// 1. 创建复合配置,指定使用 OverrideCombiner(覆盖策略)
CombinedConfiguration combined = new CombinedConfiguration(new OverrideCombiner());
// 2. 准备基础服务配置
Configuration sysConfig = new BaseConfiguration();
sysConfig.setProperty("version", "2.0.0");
// 3. 准备数据库配置
Configuration dbConfig = new BaseConfiguration();
dbConfig.setProperty("host", "127.0.0.1");
dbConfig.setProperty("port", 3306);
// 4. 组装到复合配置中
// 将 sysConfig 直接挂载到根节点,命名为 "system"
combined.addConfiguration(sysConfig, "system");
// 将 dbConfig 挂载到 "database" 节点下(使用 at 参数指定挂载路径)
combined.addConfiguration(dbConfig, "dbSource", "database");
// 5. 读取挂载后的树形路径
System.out.println("系统版本: " + combined.getString("version"));
System.out.println("数据库 Host: " + combined.getString("database.host")); // 自动带有 database. 前缀
System.out.println("数据库 Port: " + combined.getInt("database.port"));
// 6. 根据配置源名称提取原配置
Configuration extractedDb = combined.getConfiguration("dbSource");
System.out.println("提取出的原 DB Host: " + extractedDb.getString("host"));
}
}
3. 声明式复合配置:使用 CombinedConfigurationBuilder 解析定义文件
在大型项目中,直接使用硬编码组装配置不易维护。commons-configuration2 允许定义一个 config-definition.xml 声明文件,由 CombinedConfigurationBuilder 自动解析并构建整个复合配置树。
(1) 定义主描述文件 config-definition.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<header>
<!-- 定义系统属性/环境变量策略 -->
<result resultClass="org.apache.commons.configuration2.CombinedConfiguration">
<nodeCombiner config-class="org.apache.commons.configuration2.tree.OverrideCombiner"/>
</result>
</header>
<override>
<!-- 优先级 1: 系统环境变量/JVM 参数 -->
<system/>
<!-- 优先级 2: 外部开发环境配置文件 -->
<properties fileName="app-dev.properties" config-name="dev-props"/>
<!-- 优先级 3: 默认 XML 配置文件,挂载到 /app 节点下 -->
<xml fileName="default-config.xml" config-name="xml-default" at="app"/>
</override>
</configuration>
(2) Java 加载与读取代码
java
import org.apache.commons.configuration2.CombinedConfiguration;
import org.apache.commons.configuration2.builder.combined.CombinedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
public class CombinedBuilderDemo {
public static void main(String[] args) throws Exception {
// 1. 初始化 CombinedConfigurationBuilder
CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
// 2. 指定定义文件的位置
builder.configure(new Parameters().fileBased().setFileName("config-definition.xml"));
// 3. 获取构建完成的复合配置对象
CombinedConfiguration config = builder.getConfiguration();
// 4. 读取配置(优先从系统属性读,读不到找 app-dev.properties,再读不到找 default-config.xml)
System.out.println("java.version (From System): " + config.getString("java.version"));
System.out.println("应用名称 (From Properties/XML): " + config.getString("app.name"));
}
}
5.4、最佳实践
- 区分策略 :OverrideCombiner vs MergeCombiner:
- 在使用 CombinedConfiguration 时,若希望高优先级的配置直接替换掉低优先级的配置项,请选择 OverrideCombiner(默认)。
- 若希望合并不同配置文件中的同名列表(如:把 file1.xml 和 file2.xml 里的 <user> 列表合并为一个大列表),必须指定为 MergeCombiner。
- 组合配置的写入行为 :
对 CompositeConfiguration 调用 setProperty() 时,默认会修改其内部第一个可写的配置源(或通过 addConfiguration(config, true) 显式指定的内存配置源)。为避免隐蔽的变更覆盖问题,建议将复合/组合配置作为只读视图使用。 - 结合 CombinedConfigurationBuilder 管理大型项目:
对于企业级多环境配置管理,强烈建议采用声明式的 config-definition.xml 文件管理配置层级,将环境隔离(Dev / Test / Prod)逻辑集中写在定义文件中,保持 Java 代码简洁规范。
6、参数构造器
在 commons-configuration2 中,参数构造器(Parameters/Builder Parameters) 是与 2.x 版本全新的配置构建器(ConfigurationBuilder) 深度绑定的核心组件。
其核心作用是:采用流式 API(Fluent API) 和类型安全的方式,为各种不同类型(Properties、XML、YAML、Combined 等)的配置源提供统一的初始化参数配置(如文件编码、路径、分隔符策略、Schema 校验等)。
6.1、核心类与 API 继承体系
commons-configuration2 采用工厂类 Parameters 统一管理所有特定配置类型的参数构建器。
java
Parameters (工厂类,生成各类 BuilderParameters)
│
├── basic() ───────────────► BasicBuilderParameters (基础通用参数)
├── fileBased() ───────────► FileBasedBuilderParameters (文件读取/编码参数)
├── properties() ──────────► PropertiesBuilderParameters (属性文件特有参数)
├── xml() ─────────────────► XMLBuilderParameters (XML Schema/表达式引擎参数)
├── hierarchical() ────────► HierarchicalBuilderParameters (层级/树形参数)
├── combined() ────────────► CombinedBuilderParameters (多源复合配置参数)
└── database() ────────────► DatabaseBuilderParameters (数据库 JDBC 配置参数)
6.2、核心 API
1. BasicBuilderParameters(基础通用 API)
所有参数构造器的基类,提供最底层的通用控制:
setThrowExceptionOnMissing(boolean):当查询不存在的 Key 时是否抛出异常(默认返回 null 或默认值)。setListDelimiterHandler(ListDelimiterHandler):设置列表分隔符解析器(如针对逗号 , 分割的字符串解析策略)。setPrefixLookups(Map) / setParentInterpolator(...):设置变量插值与前缀查找策略。
2. FileBasedBuilderParameters(基于文件的 API)
针对所有文件类型(Properties、XML、INI、YAML 等)的通用参数:
setFileName(String) / setFile(File) / setURL(URL):指定配置文件的加载路径。setEncoding(String):显式指定文件字符编码(如 "UTF-8")。setReloadingDetectorFactory(ReloadingDetectorFactory):设置文件变更检测工厂,用于动态重载。setFileSystem(FileSystem):自定义文件系统(如从远程 HTTP/FTP 加载)。
3. PropertiesBuilderParameters(Properties 特有 API)
setIncludesAllowed(boolean):是否允许在 properties 中使用 include = xxx.properties 引入其他文件。setLayout(PropertiesConfigurationLayout):自定义属性文件的注释和格式布局。setIOFactory(IOFactory):自定义 properties 文件读写逻辑。
4. XMLBuilderParameters(XML 特有 API)
setValidating(boolean):是否开启 DTD/Schema 校验。setSchemaValidation(boolean):是否开启严格的 XML Schema 校验。setExpressionEngine(ExpressionEngine):设置节点路径解析引擎(如切换为 XPathExpressionEngine)。
6.3、使用示例
1. 使用 Parameters.properties() 构造 Properties 参数
演示如何通过流式链式调用设置编码、路径以及禁用列表分隔符:
java
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
public class PropertiesParametersDemo {
public static void main(String[] args) throws Exception {
// 1. 初始化工厂
Parameters params = new Parameters();
// 2. 通过 params.properties() 构建并填充参数
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(
params.properties()
.setFileName("app.properties") // 设置文件名
.setEncoding("UTF-8") // 设置编码
.setThrowExceptionOnMissing(true) // 缺失 key 抛出异常
.setListDelimiterHandler(new DisabledListDelimiterHandler()) // 禁用逗号分割列表
.setIncludesAllowed(true) // 允许 include 语法
);
PropertiesConfiguration config = builder.getConfiguration();
System.out.println("应用名称: " + config.getString("app.name"));
}
}
2. 使用 Parameters.xml() 构造 XML 树形解析参数
演示为 XML 配置文件配置 Schema 校验与 XPath 解析引擎参数:
java
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
public class XmlParametersDemo {
public static void main(String[] args) throws Exception {
Parameters params = new Parameters();
// 构造针对 XMLConfiguration 的专属参数
FileBasedConfigurationBuilder<XMLConfiguration> builder =
new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
.configure(
params.xml()
.setFileName("app-config.xml")
.setEncoding("UTF-8")
.setValidating(false) // 关闭常规校验
.setExpressionEngine(new XPathExpressionEngine()) // 切换路径解析引擎为 XPath
);
XMLConfiguration config = builder.getConfiguration();
// 使用 XPath 语法读取
System.out.println("数据库 Host: " + config.getString("database/host"));
}
}
3. 参数克隆与复用(clone() 模式)
在实际项目中,很多配置文件的基础参数(如编码 UTF-8、抛异常策略等)是统一的。BuilderParameters 对象支持克隆,方便快速创建相似配置:
java
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.FileBasedBuilderParameters;
import org.apache.commons.configuration2.builder.fluent.Parameters;
public class ParameterReuseDemo {
public static void main(String[] args) throws Exception {
Parameters params = new Parameters();
// 1. 定义公共基础文件参数
FileBasedBuilderParameters sharedParams = params.fileBased()
.setEncoding("UTF-8")
.setThrowExceptionOnMissing(true);
// 2. 复用模板加载 config-a.properties
FileBasedConfigurationBuilder<PropertiesConfiguration> builderA =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(sharedParams.clone().setFileName("config-a.properties"));
// 3. 复用模板加载 config-b.properties
FileBasedConfigurationBuilder<PropertiesConfiguration> builderB =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(sharedParams.clone().setFileName("config-b.properties"));
System.out.println("Config A 加载完成: " + builderA.getConfiguration().getString("module.a.name"));
System.out.println("Config B 加载完成: " + builderB.getConfiguration().getString("module.b.name"));
}
}
4. 自定义/扩展参数构造器
如果需要注入自定义的插值策略或合并行为,可以通过 BasicBuilderParameters.setPrefixLookups() 自定义参数:
java
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.interpol.Lookup;
import java.util.HashMap;
import java.util.Map;
public class CustomParametersDemo {
public static void main(String[] args) throws Exception {
Parameters params = new Parameters();
// 自定义一个前缀 Lookup (如 ${custom:key})
Map<String, Lookup> customLookups = new HashMap<>();
customLookups.put("custom", key -> "PREFIX_" + key.toUpperCase());
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(
params.properties()
.setFileName("app.properties")
.setPrefixLookups(customLookups) // 注入自定义前缀查找
);
PropertiesConfiguration config = builder.getConfiguration();
}
}
6.4、最佳实践
- 统一使用 Parameters 工厂入口:
始终使用 new Parameters().() 来创建特定的参数对象。不要直接使用 new FileBasedBuilderParametersImpl(),工厂方法能确保类型推导正确并返回标准的 Fluent 接口。 - 多线程/多文件加载时注意 clone():
BuilderParameters 实例在被 configure() 传入构建器后,其内部状态可能会被修改或绑定。如果需要批量/复用同一套通用配置(如相同的字符编码、格式策略),务必使用 .clone() 进行副本深拷贝后再传入新的 Builder。 - 针对含有逗号的值处理:
commons-configuration2 默认会把包含 , 的字符串切分为 List。如果你的配置项包含逗号(如数据库 URL 或正则),务必在参数中显式设置 .setListDelimiterHandler(new DisabledListDelimiterHandler())。