ApacheCommons——commons-configuration2(多数据源配置统一管理与热加载)

commons-configuration2(多数据源配置统一管理与热加载)

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())。
相关推荐
kyle~1 小时前
奇异值分解(SVD)
人工智能·算法·机器学习
HugoStudio_SWAN1 小时前
洛谷 B4502 [GESP202603 四级] 礼盒排序——多关键字排序与排名计数法
算法
乌夷1 小时前
JavaScript 的事件循环
开发语言·javascript·ecmascript
星星落进兜里1 小时前
Java虚拟机面试题-补充
java·开发语言
计科杨某人1 小时前
简单算法题(基础入门题)
c++·算法·题解·入门·基础算法
Q一件事1 小时前
RWEQ计算——Wf因子
算法
zander2581 小时前
LeetCode 64. 最小路径和
算法·深度优先
W658034194 小时前
Meta Muse Glimmer 30B开源深度拆解:Apache 2.0+投机解码,24GB显卡跑本地Agent
ai·开源·大模型·apache·deepseek
吴声子夜歌4 小时前
ApacheCommons——commons-pool2(高性能通用对象池框架)
java·开发语言·apache