Apache Commons CLI 快速入门与集成示例
Commons CLI 1.11.0 快速入门:依赖引入、三步跑通基础解析、缺参异常与互斥选项组处理、Properties 默认值、SyncTool 综合集成示例,附同类命令行解析库对比。
Apache Commons CLI 快速入门与集成示例
目标:从零跑通 Apache Commons CLI 1.11.0——引入依赖、三步写出第一个命令行解析器、掌握缺参异常/互斥选项组/默认值等进阶用法,并以 SyncTool 综合示例收尾,已在 Ubuntu 22.04(OpenJDK 17.0.20.1、Maven 3.6.3)实测通过,CentOS 8(OpenJDK 17.0.1、Maven 3.5.4)交叉复测结果一致。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS,x86_64,内核 6.8.0-138-generic(交叉复测:CentOS Linux 8)
- JDK:OpenJDK 17.0.20.1;CentOS 8 复测环境为 OpenJDK 17.0.1
- 构建工具:Apache Maven 3.6.3;CentOS 8 复测环境为 Maven 3.5.4
- 组件版本:Apache Commons CLI 1.11.0(发行包 bin.tar.gz 与 Maven 中央仓库 jar)
- 实验目录:
/opt/lab/cli(demos示例类、dist发行包、projMaven 工程) - 执行用户:root
二、认识命令行解析与 Commons CLI
命令行解析(Command Line Parsing)指把 main(String[] args) 收到的原始参数数组,变成结构化的「选项 + 参数值」,Commons CLI 就是 Apache 为此提供的轻量库:一个 jar、零第三方依赖、经典的三段式 API(定义 Options → 解析 → 取值)。
💡 技术拓展:Java 世界做命令行解析不止 Commons CLI 一家,主流库横向对比——
库 风格 依赖 特点 适用场景 Apache Commons CLI 声明式 API 零依赖 老牌稳定、API 简单、组与默认值齐全 传统工具、最小依赖工程 picocli 注解驱动 单 jar 注解即定义、自动生成帮助/补全、子命令强 新项目、复杂多子命令 CLI JCommander 注解驱动 零依赖(自带) 参数对象绑定、类型转换强 中型工具 args4j 注解驱动 单 jar 轻量、与 Jetty/Kohsuke 生态近 简单小工具 本文实际采用 Commons CLI:无需注解处理器、零传递依赖,发行包直接可用,与 JDK 8+ 运行环境兼容性最好。
库的安装(JDK/Maven 环境搭建与发行包获取)已在《Ubuntu 22.04 安装 Apache Commons CLI 指南》与《CentOS 8 安装 Apache Commons CLI 指南》中从零实测,本篇聚焦「引入依赖之后的编码用法」。
三、前置条件与依赖引入
3.1 环境自检
java -version 2>&1 | head -1
javac -version
mvn -version | head -1
openjdk version "17.0.20.1" 2026-08-18
javac 17.0.20.1
Apache Maven 3.6.3
3.2 方式一:Maven 依赖(推荐)
在工程 pom.xml 的 <dependencies> 中声明坐标:
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.11.0</version>
</dependency>
逐项说明:groupId/artifactId 固定为 commons-cli:commons-cli;version 建议固定到具体小版本(如 1.11.0)以保证构建可复现。声明后 mvn -B dependency:tree | tail -8 核查:
[INFO] lab:cli-demo:jar:1.0.0
[INFO] \- commons-cli:commons-cli:jar:1.11.0:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.862 s
[INFO] Finished at: 2026-10-13T06:28:33Z
[INFO] ------------------------------------------------------------------------
3.3 方式二:手动 jar 直跑
不用 Maven 时,下载发行包取 jar 并校验完整性:
mkdir -p /opt/lab/cli/dist && cd /opt/lab/cli/dist
wget -q https://downloads.apache.org/commons/cli/binaries/commons-cli-1.11.0-bin.tar.gz \
https://downloads.apache.org/commons/cli/binaries/commons-cli-1.11.0-bin.tar.gz.sha512
sha512sum commons-cli-1.11.0-bin.tar.gz && cat commons-cli-1.11.0-bin.tar.gz.sha512
tar xzf commons-cli-1.11.0-bin.tar.gz
dce3ebfb3ba0af9c9d67df7b83ec7348a07d436c47b0be06a1646e19bf652cb4cbe6b6c0b303e4dd0883cf697ca32587108d057b454a9cc8fa6553251114b44f commons-cli-1.11.0-bin.tar.gz
dce3ebfb3ba0af9c9d67df7b83ec7348a07d436c47b0be06a1646e19bf652cb4cbe6b6c0b303e4dd0883cf697ca32587108d057b454a9cc8fa6553251114b44f
两个哈希一致即校验通过(Apache 的 .sha512 文件只含裸哈希,直接 sha512sum -c 该文件会报 no properly formatted SHA512 checksum lines found;除上面的比对法外,也可用 printf '%s %s\n' "$(cat commons-cli-1.11.0-bin.tar.gz.sha512)" commons-cli-1.11.0-bin.tar.gz | sha512sum -c - 拼成标准格式自动比对)。解压后 jar 位于 commons-cli-1.11.0/commons-cli-1.11.0.jar,后续命令中的 -cp 直接指向它。
四、快速入门:三步跑通第一个解析器
第一步,创建示例目录并写 CliBasic.java:
mkdir -p /opt/lab/cli/demos && cd /opt/lab/cli/demos
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
public class CliBasic {
public static void main(String[] args) {
Options options = new Options();
options.addOption("v", "verbose", false, "打印详细日志");
options.addOption(Option.builder("n").longOpt("name").hasArg().argName("NAME").desc("用户名").get());
options.addOption(Option.builder("c").longOpt("count").hasArg().argName("COUNT").type(Integer.class).desc("重试次数").get());
try {
CommandLine cmd = new DefaultParser().parse(options, args);
System.out.println("verbose = " + cmd.hasOption("v"));
if (cmd.hasOption("n")) {
System.out.println("name = " + cmd.getOptionValue("name"));
}
if (cmd.hasOption("c")) {
Integer count = (Integer) cmd.getParsedOptionValue("c");
System.out.println("count = " + count + "(加 1 后 = " + (count + 1) + ")");
}
for (String rest : cmd.getArgs()) {
System.out.println("剩余参数 = " + rest);
}
} catch (ParseException e) {
System.err.println("解析失败: " + e.getMessage());
}
}
}
第二步,编译(类路径指向发行包 jar):
javac -encoding UTF-8 -cp /opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar CliBasic.java
编译无输出即成功。第三步,运行:
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliBasic -v --name Alice -c 3
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliBasic --verbose --name Bob f1.log f2.log
verbose = true
name = Alice
count = 3(加 1 后 = 4)
verbose = true
name = Bob
剩余参数 = f1.log
剩余参数 = f2.log
三步读法:Options 收集选项定义(addOption 三参重载定义布尔开关,Option.builder 链式定义带参选项);DefaultParser.parse 完成解析;CommandLine 按 hasOption/getOptionValue/getParsedOptionValue 取值,未被选项消费的 f1.log f2.log 落在 getArgs()。短选项 -v 与长选项 --verbose 等价。
五、进阶用法示例
5.1 必填选项与缺参异常
必填选项缺失、选项缺值是两类最常见的输入错误。CliMissing.java 分类捕获:
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
public class CliMissing {
public static void main(String[] args) {
Options options = new Options();
options.addRequiredOption("u", "url", true, "服务地址(必填)");
options.addOption("p", "port", true, "服务端口");
try {
CommandLine cmd = new DefaultParser().parse(options, args);
System.out.println("url = " + cmd.getOptionValue("url"));
} catch (MissingOptionException e) {
System.err.println("缺少必填选项: " + e.getMessage());
} catch (MissingArgumentException e) {
System.err.println("选项缺少参数: " + e.getMessage());
} catch (ParseException e) {
System.err.println("解析失败: " + e.getMessage());
}
}
}
javac -encoding UTF-8 -cp /opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar CliMissing.java
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliMissing
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliMissing -u
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliMissing -u http://localhost:8080 -p 9000
缺少必填选项: Missing required option: u
选项缺少参数: Missing argument for option: u
url = http://localhost:8080
MissingOptionException 对应「必填选项整个缺失」,MissingArgumentException 对应「选项在但没给值」;二者都是 ParseException 子类,捕获时须放在 ParseException 之前。
5.2 互斥选项组
一组「只能选一个」的选项用 OptionGroup 表达。CliGroup.java:
import org.apache.commons.cli.AlreadySelectedException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
public class CliGroup {
public static void main(String[] args) {
Options options = new Options();
OptionGroup group = new OptionGroup();
group.addOption(Option.builder("x").longOpt("xml").desc("输出 XML 格式").get());
group.addOption(Option.builder("j").longOpt("json").desc("输出 JSON 格式").get());
group.addOption(Option.builder("y").longOpt("yaml").desc("输出 YAML 格式").get());
group.setRequired(true);
options.addOptionGroup(group);
try {
CommandLine cmd = new DefaultParser().parse(options, args);
for (Option o : cmd.getOptions()) {
System.out.println("选择格式: " + o.getLongOpt());
}
} catch (AlreadySelectedException e) {
System.err.println("互斥选项重复: " + e.getMessage());
} catch (MissingOptionException e) {
System.err.println("缺少必选组: " + e.getMessage());
} catch (ParseException e) {
System.err.println("解析失败: " + e.getMessage());
}
}
}
javac -encoding UTF-8 -cp /opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar CliGroup.java
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliGroup -j
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliGroup -x -j
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliGroup
选择格式: json
互斥选项重复: The option 'j' was specified but an option from this group has already been selected: 'x'
缺少必选组: Missing required option: [-x 输出 XML 格式, -j 输出 JSON 格式, -y 输出 YAML 格式]
setRequired(true) 让组成为必选;组内重复选择抛 AlreadySelectedException,一个不选抛 MissingOptionException 并列出候选清单。
5.3 帮助文本
HelpFormatter 一行打印标准 usage/help。CliHelp.java:
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
public class CliHelp {
public static void main(String[] args) {
Options options = new Options();
options.addOption("h", "help", false, "显示帮助后退出");
options.addOption("v", "verbose", false, "打印详细日志");
options.addOption(Option.builder("u").longOpt("url").hasArg().argName("URL").desc("服务地址").get());
options.addOption(Option.builder("p").longOpt("port").hasArg().argName("PORT").desc("服务端口,默认 8080").get());
try {
CommandLine cmd = new DefaultParser().parse(options, args);
if (args.length == 0 || cmd.hasOption("h")) {
HelpFormatter formatter = HelpFormatter.builder().get();
formatter.printHelp("cli-help-demo [options] <files...>",
"示例:cli-help-demo -u http://localhost:8080 -p 9000 f1.log f2.log",
options,
"官网:https://commons.apache.org/proper/commons-cli/");
}
} catch (ParseException e) {
System.err.println("解析失败: " + e.getMessage());
}
}
}
javac -encoding UTF-8 -cp /opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar CliHelp.java
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliHelp --help
usage: cli-help-demo [options] <files...>
示例:cli-help-demo -u http://localhost:8080 -p 9000 f1.log f2.log
-h,--help 显示帮助后退出
-p,--port <PORT> 服务端口,默认 8080
-u,--url <URL> 服务地址
-v,--verbose 打印详细日志
官网:https://commons.apache.org/proper/commons-cli/
printHelp 四参依次是语法行、头说明、选项集合、页脚说明;<URL>/<PORT> 占位来自各选项的 argName。
5.4 Properties 默认值
配置文件形态的默认值可用 Properties 版 parse 实现。CliProps.java:
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import java.util.Properties;
public class CliProps {
public static void main(String[] args) throws Exception {
Options options = new Options();
options.addOption("h", "host", true, "数据库主机");
options.addOption("p", "port", true, "数据库端口");
Properties defaults = new Properties();
defaults.setProperty("host", "127.0.0.1");
defaults.setProperty("port", "5432");
// 三参 parse:命令行未给出的选项回退到 Properties 里的默认值
CommandLine cmd = new DefaultParser().parse(options, args, defaults);
System.out.println("host = " + cmd.getOptionValue("host", "未提供"));
System.out.println("port = " + cmd.getOptionValue("port", "未提供"));
}
}
javac -encoding UTF-8 -cp /opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar CliProps.java
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliProps
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" CliProps -h db.internal
host = 127.0.0.1
port = 5432
host = db.internal
port = 5432
命令行未给出的带参选项回退到 Properties 同名键;「命令行优先、配置兜底」的语义一行实现。
六、综合集成示例:SyncTool
把前面的要素组合成一个真实场景工具:数据同步命令行,含必填选项、必选互斥组、整型参数与错误时自动打印帮助。
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
public class SyncTool {
public static void main(String[] args) {
Options options = new Options();
options.addOption("v", "verbose", false, "打印详细日志");
options.addOption(Option.builder("s").longOpt("source").hasArg().argName("URL").required().desc("源端 JDBC 地址(必填)").get());
options.addOption(Option.builder("t").longOpt("target").hasArg().argName("URL").required().desc("目标端 JDBC 地址(必填)").get());
options.addOption(Option.builder().longOpt("batch-size").hasArg().argName("N").type(Integer.class).desc("每批同步行数").get());
options.addOption(Option.builder("o").longOpt("output").hasArg().argName("FILE").desc("同步报告输出文件").get());
OptionGroup mode = new OptionGroup();
mode.addOption(Option.builder().longOpt("full").desc("全量同步").get());
mode.addOption(Option.builder().longOpt("incremental").desc("增量同步").get());
mode.setRequired(true);
options.addOptionGroup(mode);
try {
CommandLine cmd = new DefaultParser().parse(options, args);
if (cmd.hasOption("verbose")) {
System.out.println("[sync] 详细日志已开启");
}
String modeSel = cmd.hasOption("full") ? "full" : "incremental";
int batchSize = cmd.hasOption("batch-size") ? (Integer) cmd.getParsedOptionValue("batch-size") : 1000;
System.out.println("[sync] " + modeSel + " 模式:从 " + cmd.getOptionValue("source")
+ " 同步到 " + cmd.getOptionValue("target") + ",每批 " + batchSize + " 行");
if (cmd.hasOption("output")) {
System.out.println("[sync] 报告将写入 " + cmd.getOptionValue("output"));
}
} catch (ParseException e) {
System.err.println("参数错误:" + e.getMessage());
HelpFormatter.builder().get().printHelp("sync-tool [options]", options);
}
}
}
逐项说明:
required():Builder 链上直接声明必填,-s/-t缺一即报错。- 必选互斥组 mode:
--full与--incremental二选一,业务层无需再写互斥校验。 type(Integer.class)+ 默认 1000:--batch-size转整型,未提供时业务默认值兜底。catch (ParseException)中打印帮助:任何解析失败统一走「错误消息 + usage」输出,符合 CLI 工具惯例。
编译并实测三种输入:
javac -encoding UTF-8 -cp /opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar SyncTool.java
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" SyncTool --full -s jdbc:mysql://src:3306/db -t jdbc:mysql://dst:3306/db --batch-size 500 -v
[sync] 详细日志已开启
[sync] full 模式:从 jdbc:mysql://src:3306/db 同步到 jdbc:mysql://dst:3306/db,每批 500 行
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" SyncTool
参数错误:Missing required options: s, t, [--full 全量同步, --incremental 增量同步]
usage: sync-tool [options]
--batch-size <N> 每批同步行数
--full 全量同步
--incremental 增量同步
-o,--output <FILE> 同步报告输出文件
-s,--source <URL> 源端 JDBC 地址(必填)
-t,--target <URL> 目标端 JDBC 地址(必填)
-v,--verbose 打印详细日志
java -cp ".:/opt/lab/cli/dist/commons-cli-1.11.0/commons-cli-1.11.0.jar" SyncTool -s jdbc:mysql://src:3306/db
参数错误:Missing required options: t, [--full 全量同步, --incremental 增量同步]
usage: sync-tool [options]
--batch-size <N> 每批同步行数
--full 全量同步
--incremental 增量同步
-o,--output <FILE> 同步报告输出文件
-s,--source <URL> 源端 JDBC 地址(必填)
-t,--target <URL> 目标端 JDBC 地址(必填)
-v,--verbose 打印详细日志
正常输入按解析结果输出执行计划;缺参输入一次性列出全部缺失项(含必选组候选),并自动打印完整帮助,无需业务代码参与。
七、常见错误速查
所有解析错误都是 ParseException 子类,按异常类型分类处理即可:
| 异常 | 触发场景(本篇实测消息) | 处理建议 |
|---|---|---|
| MissingOptionException | Missing required option: u(必填选项缺失) | catch 后提示并打印帮助 |
| MissingArgumentException | Missing argument for option: u(选项缺参数值) | 同上;注意它排在 ParseException 之前捕获 |
| AlreadySelectedException | The option 'j' was specified but an option from this group has already been selected: 'x'(互斥组重复) | 提示二选一 |
| AmbiguousOptionException | Ambiguous option: '--nam' (could be: 'name', 'nameserver')(部分匹配歧义) | 用全名,或 new DefaultParser(false) 关闭部分匹配 |
| UnrecognizedOptionException | Unrecognized option: --nam(无法识别的选项) | 检查拼写;严格模式下必须写全名 |
统一处理模式:
try {
CommandLine cmd = new DefaultParser().parse(options, args);
// 正常业务
} catch (ParseException e) {
System.err.println("参数错误:" + e.getMessage());
HelpFormatter.builder().get().printHelp("your-tool [options]", options);
}
ParseException 是全部解析异常的公共父类,不确定细分类型时按此兜底。注意 org.apache.commons.cli.HelpFormatter 在 1.11.0 中已整个类标记 @Deprecated(javap -v 可见类级 Deprecated: true),new HelpFormatter() 与 builder().get() 编译时都会触发 deprecation 告警;官方在 org.apache.commons.cli.help 包提供了新版帮助格式化器(HelpFormatter、TextHelpAppendable 等,jar tf 可见),过渡期沿用旧类功能完全正常,仅编译告警。
八、参考链接
- Apache Commons CLI 官网:https://commons.apache.org/proper/commons-cli/
- 用户指南(更多场景示例):https://commons.apache.org/proper/commons-cli/usage.html
- 1.11.0 Javadoc:https://commons.apache.org/proper/commons-cli/javadocs/api-release/index.html
- 官方下载目录:https://downloads.apache.org/commons/cli/binaries/
- 源码仓库:https://github.com/apache/commons-cli