Apache Commons Configuration 常用 CLI 命令与工具详解

以 jshell 与 javac/java -cp 实操 Apache Commons Configuration 2.15.1:properties/XML/INI/YAML/JSON 最小示例、jar 清单与典型报错,回显全部实测。

Apache Commons ConfigurationApache Commons Configuration2026/10/13

Apache Commons Configuration 常用 CLI 命令与工具详解

目标:在没有 IDE、不搭工程的情况下,用 JDK 自带命令行工具(jshell、javac/java -cp、jar、javap)直接加载并验证 Apache Commons Configuration 2.15.1 的各类配置源,给出每个数据源所需 jar 清单、最小示例与实测输出,已在 Ubuntu 22.04(OpenJDK 17.0.20.1)实测通过。

一、环境信息

本篇所有命令与回显均在以下环境实测采集:

  • 操作系统:Ubuntu 22.04,x86_64
  • JDK:OpenJDK 17.0.20.1(含 jshell 17.0.20.1)
  • 组件版本:org.apache.commons:commons-configuration2:2.15.1
  • jar 目录:/opt/commons-config/lib/(全部依赖),/opt/commons-config/lib-core/(仅必选依赖)
  • 工作目录:/opt/commons-config/demos(源码与编译输出)
  • 执行用户:root

jar 清单(ls -l /opt/commons-config/lib/,实测):

total 4896
-rw------- 1 root root  689129 Oct 13 08:00 commons-configuration2-2.15.1.jar
-rw-r--r-- 1 root root  609182 Oct 13 08:00 commons-io-2.22.0.jar
-rw-r--r-- 1 root root  713862 Oct 13 08:00 commons-lang3-3.20.0.jar
-rw-r--r-- 1 root root   74010 Oct 13 08:00 commons-logging-1.3.6.jar
-rw-r--r-- 1 root root  264873 Oct 13 08:00 commons-text-1.15.0.jar
-rw-r--r-- 1 root root     554 Oct 13 08:00 jackson-annotations-2.21.3.jar
-rw-r--r-- 1 root root  594142 Oct 13 08:00 jackson-core-2.21.3.jar
-rw-r--r-- 1 root root 1702259 Oct 13 08:02 jackson-databind-2.21.3.jar
-rw-r--r-- 1 root root  340068 Oct 13 08:00 snakeyaml-2.6.jar

(jackson-annotations-2.21.3.jar 是当时下载失败的 404 HTML 残片,仅 554 字节;正确文件名是 jackson-annotations-2.21.jar,见第六章。)

二、工具链总览

Commons Configuration 是一个 Java 库,官方没有附带任何命令行可执行程序(发行包 bin 包内只有 jar、apidocs 与许可证文件,tar tzf 可验证)。所谓「CLI 实操」是用 JDK 自带工具直接驱动该库:

工具用途本篇场景
jshellJDK 自带 REPL,脚本模式 .jsh免编译直接读改存 properties
javac / java -cp编译运行单文件验证程序XML/INI/YAML/JSON 各源验证
jar tf列出 jar 内类排查「类在哪个 jar」
javap反汇编类的方法签名确认 API 是否存在
tar tzf / tar xzf检查与解压发行包确认 bin 包内容

jar tf 快速验证(实测):

jar tf /opt/commons-config/lib/commons-configuration2-2.15.1.jar | grep -c "class"

回显(实测):

401

三、各数据源的 classpath 需求

先给结论表,再逐一实测验证(❌ 报错原文与 ✅ 修复后的输出都在后文):

数据源/能力主 jar 外必需 jarMaven 坐标
properties(FileHandler 直连)无必选 4 件套即可
XML无必选 4 件套即可
INI无必选 4 件套即可
properties/XML/任意(Parameters Builder 工厂)+ commons-beanutils-1.11.0.jarcommons-beanutils:commons-beanutils:1.11.0(pom 中 optional)
YAML(YAMLConfiguration)+ snakeyaml-2.6.jarorg.yaml:snakeyaml:2.6(optional)
JSON(JSONConfiguration)+ jackson-databind-2.21.3.jar + jackson-core-2.21.3.jar + jackson-annotations-2.21.jarcom.fasterxml.jackson.core:jackson-databind:2.21.3(optional)
XML XPath 表达式引擎+ commons-jxpath-1.4.0.jarcommons-jxpath:commons-jxpath:1.4.0(optional)

必选 4 件套(pom 非 optional 依赖,Maven dependency:tree 实测):commons-lang3-3.20.0.jar、commons-text-1.15.0.jar、commons-logging-1.3.6.jar、commons-io-2.22.0.jar。

💡 技术拓展:数据源与依赖/能力对照(语法、层级、类型、热更新维度的完整对比见安装指南)

数据源Configuration 类额外依赖层级原生类型
propertiesPropertiesConfiguration无点分扁平字符串
XMLXMLConfiguration无树形+属性字符串
INIINIConfiguration无section 分组字符串
YAMLYAMLConfigurationsnakeyaml树形数字/布尔保留
JSONJSONConfigurationjackson-databind树形数字/布尔保留

本文逐一实测 properties/XML/INI/YAML/JSON 五条路线。

四、jshell 实操:免编译读写 properties

4.1 启动 jshell

jshell --version

回显(实测):

jshell 17.0.20.1

--class-path 支持通配符,把整个 lib 目录挂进 REPL:

jshell --class-path "/opt/commons-config/lib/*" jshell-session.jsh

4.2 脚本内容(jshell-session.jsh)

import org.apache.commons.configuration2.*;
import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
import org.apache.commons.configuration2.io.FileHandler;
import java.nio.file.*;

Files.write(Path.of("cli.properties"), "app.name = cli-demo\napp.port = 9090\nservers = 10.0.0.1,10.0.0.2\n".getBytes());

var props = new PropertiesConfiguration();
props.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
var handler = new FileHandler(props);
handler.setFileName("cli.properties");
handler.load();
System.out.println("app.name = " + props.getString("app.name"));
System.out.println("app.port = " + props.getInt("app.port"));
System.out.println("servers  = " + props.getList("servers"));
props.setProperty("app.port", 9191);
handler.save();
System.out.println("saved, cli.properties content:");
Files.readAllLines(Path.of("cli.properties")).forEach(System.out::println);

/exit

回显(实测):

app.name = cli-demo
app.port = 9090
servers  = [10.0.0.1,10.0.0.2]
saved, cli.properties content:
app.name = cli-demo
app.port = 9191
servers = 10.0.0.1,10.0.0.2

properties 读写闭环完成:9090 被改为 9191 并落盘。

4.3 jshell 的四个坑(全部实测踩过)

坑 1:.jsh 脚本里语句必须带分号。jshell 交互模式可以省略分号,但脚本模式的花括号块内省略分号直接报错(实测):

Error:
';' expected
    out.println("app.name = cli-demo")
                                      ^

坑 2:import a.b.* 不覆盖子包。import org.apache.commons.configuration2.* 之后用 DefaultListDelimiterHandler 报错(实测):

Error:
cannot find symbol
  symbol:   class DefaultListDelimiterHandler
props.setListDelimiterHandler(new DefaultListDelimiterHandler(','));

它实际在 convert 子包,需显式 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;。

坑 3:stdin 非交互(管道喂命令)时 REPL 抑制回显,jshell --class-path "/opt/commons-config/lib/*" - < cmds.txt 这类管道方式成功语句不打印结果;文档化的做法是在脚本里 System.out.println 显式输出。

坑 4:--execution local 会让 --class-path 失效,报大量 NoClassDefFoundError;jshell 保持默认执行引擎即可。

五、javac + java -cp 实操

5.1 单文件编译运行模板

cd /opt/commons-config/demos
javac -encoding UTF-8 -cp "/opt/commons-config/lib/*" -d classes src/com/example/XmlDemo.java
java -Dfile.encoding=UTF-8 -cp "classes:/opt/commons-config/lib/*" com.example.XmlDemo

说明:-encoding UTF-8 必带(否则中文注释源码报 unmappable character);-cp 通配符 lib/* 只匹配 jar 文件本身,不递归子目录;classes: 与 lib/* 用冒号分隔(Linux 分隔符)。

5.2 XML 读写(无需额外 jar)

XmlDemo.java 生成演示文件、读取元素与属性、改值回写:

package com.example;

import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;

import java.io.PrintWriter;

public class XmlDemo {
    public static void main(String[] args) throws Exception {
        // 准备演示 XML
        try (PrintWriter out = new PrintWriter("database.xml", "UTF-8")) {
            out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            out.println("<!-- 数据库连接配置,由 XmlDemo 生成 -->");
            out.println("<database>");
            out.println("  <url>jdbc:mysql://localhost:3306/demo</url>");
            out.println("  <user timeout=\"30\">root</user>");
            out.println("  <pool>");
            out.println("    <maxTotal>20</maxTotal>");
            out.println("    <minIdle>2</minIdle>");
            out.println("  </pool>");
            out.println("  <servers>");
            out.println("    <server>db1.example.com</server>");
            out.println("    <server>db2.example.com</server>");
            out.println("  </servers>");
            out.println("</database>");
        }

        FileBasedConfigurationBuilder<XMLConfiguration> builder =
                new FileBasedConfigurationBuilder<>(XMLConfiguration.class)
                        .configure(new Parameters().xml()
                                .setFileName("database.xml")
                                .setEncoding("UTF-8"));
        XMLConfiguration xml = builder.getConfiguration();

        // 2.x 的键从根元素下一级开始写(不同于 1.x 的 database.url 写法)
        System.out.println("url            = " + xml.getString("url"));
        System.out.println("user           = " + xml.getString("user"));
        System.out.println("user[@timeout] = " + xml.getInt("user[@timeout]"));
        System.out.println("pool.maxTotal  = " + xml.getInt("pool.maxTotal"));
        System.out.println("servers        = " + xml.getList("servers.server"));

        // 修改并回写
        xml.setProperty("pool.maxTotal", 50);
        builder.save();
        System.out.println("---- database.xml after save ----");
        new java.io.BufferedReader(new java.io.FileReader("database.xml"))
                .lines().forEach(System.out::println);
    }
}

回显(实测):

url            = jdbc:mysql://localhost:3306/demo
user           = root
user[@timeout] = 30
pool.maxTotal  = 20
servers        = [db1.example.com, db2.example.com]
---- database.xml after save ----
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- 数据库连接配置,由 XmlDemo 生成 --><database>
  <url>jdbc:mysql://localhost:3306/demo</url>
  <user timeout="30">root</user>
  <pool>
    <maxTotal>50</maxTotal>
    <minIdle>2</minIdle>
  </pool>
  <servers>
    <server>db1.example.com</server>
    <server>db2.example.com</server>
  </servers>
</database>

两个关键差异点:键不含根元素名(url 而非 database.url,2.x 键相对根元素下一级);属性用 user[@timeout] 语法读取。

5.3 INI 分节(无需额外 jar)

IniDemo.java:

package com.example;

import org.apache.commons.configuration2.INIConfiguration;
import org.apache.commons.configuration2.SubnodeConfiguration;
import org.apache.commons.configuration2.io.FileHandler;

import java.io.PrintWriter;

public class IniDemo {
    public static void main(String[] args) throws Exception {
        try (PrintWriter out = new PrintWriter("app.ini", "UTF-8")) {
            out.println("[mysql]");
            out.println("host = 127.0.0.1");
            out.println("port = 3306");
            out.println("[redis]");
            out.println("host = 127.0.0.1");
            out.println("port = 6379");
            out.println("database = 0");
        }

        INIConfiguration ini = new INIConfiguration();
        new FileHandler(ini).load("app.ini");

        // 全局视图:section.key 前缀访问
        System.out.println("mysql.host = " + ini.getString("mysql.host"));
        System.out.println("mysql.port = " + ini.getInt("mysql.port"));
        System.out.println("redis.port = " + ini.getInt("redis.port"));

        // 也可以取单个 section 的子配置
        SubnodeConfiguration redis = ini.getSection("redis");
        System.out.println("redis(host) = " + redis.getString("host"));
        System.out.println("sections    = " + ini.getSections());
    }
}

回显(实测):

mysql.host = 127.0.0.1
mysql.port = 3306
redis.port = 6379
redis(host) = 127.0.0.1
sections    = [mysql, redis]

六、YAML 与 JSON:先报错,后补依赖

6.1 YAML:缺 snakeyaml 的报错与修复

用只含必选依赖的 lib-core 目录(5 个 jar,无 snakeyaml)运行 YamlDemo(实测):

java -cp "classes:/opt/commons-config/lib-core/*" com.example.YamlDemo

回显(实测,节选首 3 行):

Exception in thread "main" java.lang.NoClassDefFoundError: org/yaml/snakeyaml/constructor/BaseConstructor
        at com.example.YamlDemo.main(YamlDemo.java:25)
Caused by: java.lang.ClassNotFoundException: org.yaml.snakeyaml.constructor.BaseConstructor

补 snakeyaml:官方 pom 声明 org.yaml:snakeyaml:2.6(optional)。

cd /opt/commons-config/lib
curl -sSLO https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.6/snakeyaml-2.6.jar
ls -l snakeyaml-2.6.jar

回显(实测):

-rw-r--r-- 1 root root 340068 Oct 13 08:00 snakeyaml-2.6.jar

YamlDemo.java:

package com.example;

import org.apache.commons.configuration2.YAMLConfiguration;
import org.apache.commons.configuration2.io.FileHandler;

import java.io.PrintWriter;
import java.util.List;

public class YamlDemo {
    public static void main(String[] args) throws Exception {
        try (PrintWriter out = new PrintWriter("app.yaml", "UTF-8")) {
            out.println("database:");
            out.println("  url: jdbc:mysql://localhost:3306/demo");
            out.println("  pool:");
            out.println("    maxTotal: 20");
            out.println("    minIdle: 2");
            out.println("servers:");
            out.println("  - db1.example.com");
            out.println("  - db2.example.com");
            out.println("feature:");
            out.println("  enabled: true");
            out.println("timeout: 30s");
        }

        YAMLConfiguration yaml = new YAMLConfiguration();
        new FileHandler(yaml).load("app.yaml");
        System.out.println("database.url        = " + yaml.getString("database.url"));
        System.out.println("database.pool.maxTotal = " + yaml.getInt("database.pool.maxTotal"));
        List<Object> servers = yaml.getList("servers");
        System.out.println("servers             = " + servers);
        System.out.println("feature.enabled     = " + yaml.getBoolean("feature.enabled"));
        System.out.println("timeout             = " + yaml.getString("timeout"));
        new FileHandler(yaml).save("app-roundtrip.properties");
        System.out.println("---- round-trip to properties ----");
        new java.io.BufferedReader(new java.io.FileReader("app-roundtrip.properties"))
                .lines().forEach(System.out::println);
    }
}

回显(实测):

database.url        = jdbc:mysql://localhost:3306/demo
database.pool.maxTotal = 20
servers             = [db1.example.com, db2.example.com]
feature.enabled     = true
timeout             = 30s
---- round-trip to properties ----
database:
  pool:
    maxTotal: 20
    minIdle: 2
  url: jdbc:mysql://localhost:3306/demo
servers:
- db1.example.com
- db2.example.com
feature:
  enabled: true
timeout: 30s

注意 round-trip 输出仍是 YAML 语法:保存格式由 Configuration 类决定(YAMLConfiguration 永远写 YAML),与目标文件扩展名无关——这是命令行验证时常见的误判点。另外 YAML 数字/布尔保留原生类型:maxTotal 读出即 int,enabled 读出即 boolean。

6.2 JSON:缺 jackson 的报错与修复

同样先无依赖运行(实测,节选首 2 行):

Exception in thread "main" java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/JavaType
        at com.example.JsonDemo.main(JsonDemo.java:23)

补 jackson:官方 pom 声明 jackson-databind 2.21.3。实测发现 Maven 中央仓库没有 jackson-annotations 2.21.3(curl 验证 404,Jackson 自 2.20 起 annotations 不再带 patch 号,bom 里 jackson.version.annotations=2.21),手动组 classpath 必须用 jackson-annotations-2.21.jar:

cd /opt/commons-config/lib
B=https://repo1.maven.org/maven2
curl -sSLO "$B/com/fasterxml/jackson/core/jackson-databind/2.21.3/jackson-databind-2.21.3.jar"
curl -sSLO "$B/com/fasterxml/jackson/core/jackson-core/2.21.3/jackson-core-2.21.3.jar"
curl -sSLO "$B/com/fasterxml/jackson/core/jackson-annotations/2.21/jackson-annotations-2.21.jar"
curl -sI "$B/com/fasterxml/jackson/core/jackson-annotations/2.21.3/jackson-annotations-2.21.3.jar" | head -1

回显(实测):

HTTP/2 404

JsonDemo.java:

package com.example;

import org.apache.commons.configuration2.JSONConfiguration;
import org.apache.commons.configuration2.io.FileHandler;

import java.io.PrintWriter;
import java.util.List;

public class JsonDemo {
    public static void main(String[] args) throws Exception {
        try (PrintWriter out = new PrintWriter("app.json", "UTF-8")) {
            out.println("{");
            out.println("  \"database\": {");
            out.println("    \"url\": \"jdbc:mysql://localhost:3306/demo\",");
            out.println("    \"pool\": {\"maxTotal\": 20, \"minIdle\": 2}");
            out.println("  },");
            out.println("  \"servers\": [\"db1.example.com\", \"db2.example.com\"],");
            out.println("  \"feature\": {\"enabled\": true},");
            out.println("  \"timeout\": \"30s\"");
            out.println("}");
        }

        JSONConfiguration json = new JSONConfiguration();
        new FileHandler(json).load("app.json");
        System.out.println("database.url           = " + json.getString("database.url"));
        System.out.println("database.pool.maxTotal = " + json.getInt("database.pool.maxTotal"));
        List<Object> servers = json.getList("servers");
        System.out.println("servers                = " + servers);
        System.out.println("feature.enabled        = " + json.getBoolean("feature.enabled"));
    }
}

回显(实测):

database.url           = jdbc:mysql://localhost:3306/demo
database.pool.maxTotal = 20
servers                = [db1.example.com, db2.example.com]
feature.enabled        = true

七、jar 与 javap 排查技巧

7.1 确认「类在哪个 jar」

jar tf /opt/commons-config/lib/snakeyaml-2.6.jar | grep "org/yaml/snakeyaml/Yaml.class"

回显(实测):

org/yaml/snakeyaml/Yaml.class

对疑似 jar 逐个执行 jar tf /opt/commons-config/lib/commons-configuration2-2.15.1.jar | grep XPathExpressionEngine 这类命令即可定位;grep -c 还能统计整个 jar 的类数量(见第二章)。

7.2 确认「方法是否存在」

不确定 API 时用 javap 看签名,避免凭记忆臆造(实测):

javap -cp /opt/commons-config/lib/commons-configuration2-2.15.1.jar org.apache.commons.configuration2.io.FileHandler | grep -E "FileHandler\(|load|save"

回显(实测节选):

  public org.apache.commons.configuration2.io.FileHandler();
  public org.apache.commons.configuration2.io.FileHandler(org.apache.commons.configuration2.io.FileBased);
  public void load() throws org.apache.commons.configuration2.ex.ConfigurationException;
  public void load(java.lang.String) throws org.apache.commons.configuration2.ex.ConfigurationException;
  public void save() throws org.apache.commons.configuration2.ex.ConfigurationException;
  public void save(java.lang.String) throws org.apache.commons.configuration2.ex.ConfigurationException;

由此可实锤:load()/save() 返回 void,不能链式调用;new FileHandler(配置对象) 构造器存在。

7.3 一个真实的坑:load(String) 后 save() 失败

先 handler.load("cli.properties") 再直接 handler.save() 会报错(jshell 实测):

Exception org.apache.commons.configuration2.ex.ConfigurationException: No file location has been set.
      at FileHandler.save (FileHandler.java:887)
      at FileHandler.save (FileHandler.java:845)

原因:load(String)/save(String) 是一次性重载,不会把路径写入 handler 的定位器;save() 无参版本要求先 setFileName(...)。修复写法:

handler.setFileName("cli.properties");
handler.load();
handler.save();

八、常见问题(FAQ)

Q1:jshell 脚本报 ';' expected?

脚本模式的块内语句必须分号结尾,见 4.3 坑 1。交互模式下单行表达式可省略分号,但贴成 .jsh 文件执行就会翻车。

Q2:NoClassDefFoundError 速查?

按缺失类名对号入座(全部实测复现过):

缺失类前缀缺的 jar补法
org/apache/commons/beanutils/commons-beanutils-1.11.0.jar用了 Parameters/Builder 工厂时必补
org/yaml/snakeyaml/snakeyaml-2.6.jarYAML 数据源
com/fasterxml/jackson/databind/jackson-databind-2.21.3.jar + jackson-core-2.21.3.jar + jackson-annotations-2.21.jarJSON 数据源
org/apache/commons/jxpath/commons-jxpath-1.4.0.jarXPathExpressionEngine

Q3:java -cp 写了目录却报 ClassNotFoundException?

通配符必须带 * 且不能加递归:-cp "classes:/opt/commons-config/lib/*" 正确;-cp "/opt/commons-config/lib" 只当普通目录(找 .class 不找 jar);lib/**/*.jar 不被支持。另外 Windows 下分隔符是分号,Linux 是冒号。

Q4:javac 报 unmappable character (0xE9) for encoding US-ASCII?

平台编码不是 UTF-8(echo $LANG 为空或 C)。解法:javac 加 -encoding UTF-8,运行带 -Dfile.encoding=UTF-8,或 export LANG=C.UTF-8 一劳永逸。

Q5:jshell 里 var 声明的配置对象为什么能直接用?

jshell 自动推断类型(var props → PropertiesConfiguration),与编译期写法等价;脚本调试通过后再固化成 .java 文件,本篇 5.1 的模板即是固化路径。

九、参考链接

  1. JShell 用户指南(脚本模式与反馈模式):https://docs.oracle.com/en/java/javase/17/jshell/introduction-jshell.html
  2. java 命令 classpath 通配符官方说明:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html
  3. Apache Commons Configuration 2.15.1 Javadoc:https://commons.apache.org/proper/commons-configuration/apidocs/index.html
  4. 2.x 用户指南·各 Configuration 实现(数据源矩阵):https://commons.apache.org/proper/commons-configuration/userguide/howto_configurationbuilders.html
  5. Maven 中央仓库 commons-configuration2 目录:https://repo1.maven.org/maven2/org/apache/commons/commons-configuration2/2.15.1/