CentOS 8 安装 Apache Commons CLI 指南

在 CentOS 8 从零安装 Apache Commons CLI 1.11.0:vault 源确认、JDK 17 与 Maven 安装及默认 JDK 切换、Maven 与手动 jar 双方案、sha512 校验与卸载,全程实测。

Apache Commons CLIApache Commons CLI2026/10/13

CentOS 8 安装 Apache Commons CLI 指南

目标:在 CentOS 8 服务器上从零搭建可编译、可运行 Apache Commons CLI 的 Java 环境,覆盖 vault 源确认、JDK 17 与 Maven 安装及多 JDK 默认项切换、Maven 依赖与官方发行包两种安装方案、sha512 校验与卸载清理,已在 CentOS 8.4.2105(OpenJDK 17.0.1、Maven 3.5.4)实测通过。

一、环境信息

本篇所有命令与回显均在以下环境中实测采集,复现时请保持相近版本:

  • 操作系统:CentOS Linux 8.4.2105,x86_64(软件源已切换至 vault 归档源,见 2.2 节)
  • JDK:OpenJDK 17.0.1(java-17-openjdk-devel,dnf AppStream 仓库)
  • 构建工具:Apache Maven 3.5.4(maven,dnf AppStream 仓库,module_el8.0.0+39)
  • 组件版本:Apache Commons CLI 1.11.0(downloads.apache.org 当前最新版,2026-10 实测)
  • 下载与校验工具:wget 1.19.5、coreutils sha512sum
  • 实验目录:/opt/cli-maven(方案 A 工程)与 /opt/downloads、/opt/javac-demo(方案 B)
  • 执行用户:root

二、前置条件

本节确认系统版本、软件源与基础工具,保证后续 dnf 安装与下载校验能顺利完成。

2.1 确认系统与网络

cat /etc/redhat-release && uname -m

回显(实测):

CentOS Linux release 8.4.2105
x86_64

确认官方下载站可达:

wget -S --spider https://downloads.apache.org/commons/cli/binaries/ 2>&1 | grep -E "HTTP/|Length:" | head -3

回显(实测):

  HTTP/1.1 200 OK
  Content-Length: 11997
Length: 11997 [text/html]

返回 HTTP/1.1 200 OK 说明官方下载站可达,可以继续安装。

2.2 确认软件源(vault 归档源)

CentOS 8 已于 2021 年底停止维护,官方 mirrorlist 已下线,系统必须指向 vault 归档源才能正常 dnf install。本实验环境的源配置如下:

grep -m2 baseurl /etc/yum.repos.d/CentOS-Linux-AppStream.repo /etc/yum.repos.d/CentOS-Linux-BaseOS.repo

回显(实测):

/etc/yum.repos.d/CentOS-Linux-AppStream.repo:baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.5.2111/AppStream/x86_64/os/
/etc/yum.repos.d/CentOS-Linux-BaseOS.repo:baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.5.2111/BaseOS/x86_64/os/

两个基础仓库的 baseurl 均指向 centos-vault/8.5.2111(CentOS 8 的最终归档版本),AppStream 仓库提供 java-17-openjdk-devel 与 maven 包。如果你的机器上 repo 文件仍是 mirrorlist= 开头的官方镜像配置,需要参照上述形态把每行 mirrorlist 注释掉、改为对应 vault baseurl 后再执行 dnf makecache。

2.3 安装下载工具 wget

dnf install -y wget

输出节选(实测首尾):

CentOS Linux 8 - AppStream                      3.4 kB/s | 4.3 kB     00:01
CentOS Linux 8 - BaseOS                         18 kB/s | 3.9 kB     00:00
Dependencies resolved.

(中段为依赖解析与安装明细,尾部:)

  libpsl-0.20.2-6.el8.x86_64    publicsuffix-list-dafsa-20180723-1.el8.noarch
  wget-1.19.5-10.el8.x86_64

Complete!
wget --version | head -1

回显(实测):

GNU Wget 1.19.5 built on linux-gnu.

wget 正常输出版本号,基础工具安装完成。

三、版本选型说明

Commons CLI 是纯 Java 命令行解析库,没有独立进程,选型核心是「库版本 + JDK 版本」的匹配。先实测确认官方当前最新版本号:

wget -qO- https://downloads.apache.org/commons/cli/binaries/ | grep -oE 'commons-cli-[0-9.]+-bin\.tar\.gz' | sort -u

回显(实测):

commons-cli-1.11.0-bin.tar.gz

(同目录还附有 commons-cli-1.11.0-bin.tar.gz.sha512 校验文件与 .asc GPG 签名文件。)

版本与 JDK 要求对照如下(1.11.0 的 class 文件编译目标实测见 jar 内 MANIFEST.MF 的 X-Compile-Target-JDK: 1.8,即 Java 8 字节码):

组件版本JDK 要求获取渠道说明
Commons CLI1.11.0Java 8+downloads.apache.org(官方最新,2025-11 发布)本文采用,sha512 实测校验通过
Commons CLI1.10.0Java 8+Maven 中央仓库 / archive.apache.org上一稳定版,存量工程常见
JDKOpenJDK 17.0.1—dnf AppStream 仓库(java-17-openjdk-devel)编译运行库示例均正常
Maven3.5.4Java 8+dnf AppStream 仓库(module_el8.0.0+39)方案 A 依赖解析使用

关键差异与选型建议:新工程直接用官方最新 1.11.0(本文实测版本),它把 Option.Builder.build() 标记为废弃并提供等价的 get()(详见《Apache Commons CLI 配置参数详解》);维护旧工程时保持其原有版本即可,1.10.0 仍可从中央仓库与 archive 站正常获取(实测 HTTP 200)。JDK 侧只需满足 Java 8 及以上,本文用 OpenJDK 17 验证。

四、安装方案 A:Maven 依赖安装(在线,推荐)

方案 A 通过 Maven 中央仓库自动拉取 commons-cli jar,依赖清单写入 pom.xml,适合所有使用 Maven 的工程。本节在全新系统上完成 JDK/Maven 安装、多 JDK 默认项切换、工程创建、依赖解析与最小验证。

4.1 安装 JDK 17 与 Maven,并处理默认 JDK

在全新 CentOS 8 上一条命令装齐:

dnf install -y java-17-openjdk-devel maven

输出节选(实测首尾):

CentOS Linux 8 - AppStream                      3.4 kB/s | 4.3 kB     00:01
CentOS Linux 8 - BaseOS                         18 kB/s | 3.9 kB     00:00
Dependencies resolved.
=================================================================================================================
 Package                                   Arch    Version                                       Repo        Size
=================================================================================================================
Installing:
 java-17-openjdk-devel                     x86_64  1:17.0.1.0.12-2.el8_5                         AppStream  5.1 M
 maven                                     noarch  1:3.5.4-5.module_el8.0.0+39+6a9b6e22          AppStream   27 k
Upgrading:

(中段为依赖安装明细,尾部:)

  xorg-x11-fonts-Type1-7.5-19.el8.noarch

Complete!

安装完成后先验证——这里会出现 CentOS 8 上最典型的坑,默认 java 不是 17:

java -version

回显(实测):

openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)

dnf 在解析 java-17-openjdk-devel 与 maven 的依赖时把 java-1.8.0-openjdk 一并带入,且其 alternatives 优先级更高(1800312),于是默认 java 命令指向 1.8。用 alternatives 查看并切换:

alternatives --display java | head -4

回显(实测):

java - status is manual.
 link currently points to /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java
/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/jre/bin/java - family java-1.8.0-openjdk.x86_64 priority 1800312
 slave alt-java: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/jre/bin/alt-java
alternatives --set java /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java && java -version 2>&1 | head -1

回显(实测):

openjdk version "17.0.1" 2021-10-19 LTS

java 已切到 17。但编译器 javac 是独立的 alternatives 组,需要单独切换,否则仍是 1.8:

javac -version

回显(实测):

javac 1.8.0_312
alternatives --set javac /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/javac && javac -version

回显(实测):

javac 17.0.1

再验证 Maven。Maven 启动脚本查找 JAVA_HOME,未设置时会选到 1.8 的运行时:

mvn -version | head -3

回显(实测):

Apache Maven 3.5.4 (Red Hat 3.5.4-5)
Maven home: /usr/share/maven
Java version: 1.8.0_312, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/jre

给当前会话显式指定 JAVA_HOME 后重新验证:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && mvn -version | head -4

回显(实测):

Apache Maven 3.5.4 (Red Hat 3.5.4-5)
Maven home: /usr/share/maven
Java version: 17.0.1, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64
Default locale: en, platform encoding: UTF-8

要让该配置持久化,把 export JAVA_HOME=/usr/lib/jvm/java-17-openjdk 追加到 /etc/profile.d/java.sh(或用户 ~/.bash_profile)。至此 java、javac、mvn 三者都运行在 17.0.1 上。

4.2 创建工程与 pom.xml

创建 Maven 工程骨架并写入最小 pom:

mkdir -p /opt/cli-maven/src/main/java/com/example

回显(实测,无输出即成功):

cat > /opt/cli-maven/pom.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>maven-cli-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>commons-cli</groupId>
      <artifactId>commons-cli</artifactId>
      <version>1.11.0</version>
    </dependency>
  </dependencies>
</project>
EOF

回显(实测,无输出即成功):

pom 逐项说明:groupId/artifactId/version 标识本示例工程;maven.compiler.source/target 设为 8,与 Commons CLI 1.11.0 的最低 Java 8 要求对齐(即便当前 JDK 是 17,产出的字节码仍是 Java 8,方便部署到低版本运行时);project.build.sourceEncoding 固定 UTF-8,保证源码里的中文字符串按 UTF-8 编译;dependencies 中的 commons-cli:commons-cli:1.11.0 就是本篇的主角,Maven 首次编译时会自动从中央仓库下载该 jar(Commons CLI 1.11.0 无传递依赖,单 jar 即可用)。

4.3 编译并解析依赖

首次编译触发插件与依赖下载:

cd /opt/cli-maven && mvn clean compile

输出节选(实测首尾):

Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.1/maven-compiler-plugin-3.1.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.1/maven-compiler-plugin-3.1.pom (10 kB at 23 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.1/maven-compiler-plugin-3.1.jar
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.1/maven-compiler-plugin-3.1.jar (43 kB at 87 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.pom
Downloaded from central: https://repo.maven.apache.org/maven2/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.pom (14 kB at 30 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar
Downloaded from central: https://repo.maven.apache.org/maven2/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar (110 kB at 155 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/commons-cli/commons-cli/1.0/commons-cli-1.0.pom
Downloaded from central: https://repo.maven.apache.org/maven2/commons-cli/commons-cli/1.0/commons-cli-1.0.pom (2.1 kB at 4.7 kB/s)
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-cli-demo ---
[INFO] BUILD SUCCESS
[INFO] Total time: 0.997 s

核对 jar 已落入本地仓库:

ls /root/.m2/repository/commons-cli/commons-cli/1.11.0/

回显(实测):

_remote.repositories
commons-cli-1.11.0.jar
commons-cli-1.11.0.jar.sha1
commons-cli-1.11.0.pom
commons-cli-1.11.0.pom.sha1

BUILD SUCCESS 加本地仓库中出现 commons-cli-1.11.0.jar(110 kB),依赖解析完成。解析 1.11.0 时 Maven 还会拉取一份 commons-cli-1.0.pom(1.11.0 的 POM 内部引用了 1.0 的坐标,见上方实测回显),本地仓库因此出现 1.0 目录,属正常现象,不影响运行时使用的版本。

4.4 最小验证代码

写一个仅依赖 Commons CLI 的验证类,覆盖「定义选项 → 解析 → 类型转换」闭环:

cat > /opt/cli-maven/src/main/java/com/example/Main.java <<'EOF'
package com.example;

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 Main {
    public static void main(String[] args) {
        Options options = new Options();
        options.addOption("v", "verbose", false, "开启详细日志输出");
        options.addOption(Option.builder("n").longOpt("name").hasArg().argName("USER")
                .desc("指定运行用户名").get());
        options.addOption(Option.builder("c").longOpt("count").hasArg().argName("N")
                .type(Integer.class).desc("任务执行次数").get());

        try {
            if (args.length == 0) {
                new HelpFormatter().printHelp("maven-cli-demo [选项] [参数]", options, true);
                return;
            }
            CommandLine line = new DefaultParser().parse(options, args);
            if (line.hasOption("v")) {
                System.out.println("verbose = true");
            }
            if (line.hasOption("n")) {
                System.out.println("name = " + line.getOptionValue("n"));
            }
            if (line.hasOption("c")) {
                Integer count = (Integer) line.getParsedOptionValue("c");
                System.out.println("count = " + count + "(Integer)");
            }
        } catch (ParseException e) {
            System.err.println("解析失败:" + e.getMessage());
            System.exit(2);
        }
    }
}
EOF

回显(实测,无输出即成功):

💡 技术拓展:Java 生态里「定义选项 → 解析 → 取值」并非只有 Commons CLI 一种写法,常见选择对比——

库定义方式学习成本典型场景
Apache Commons CLIOptions + Builder 链式低传统工具类程序的短/长选项解析
picocli注解标注字段/方法低-中新项目,支持子命令、自动补全、彩色帮助
args4j注解标注字段低轻量解析,功能面较窄
JCommander注解 + 参数对象中复杂参数校验场景

本篇验证代码采用 Commons CLI 的 Builder 链式写法,理由是与官方发行包、各类教程的形态一致、无额外传递依赖;其余库的取舍对比在《Apache Commons CLI 配置参数详解》中还有展开。

编译并运行验证类:

cd /opt/cli-maven && mvn -q compile exec:java -Dexec.args="--name tom --count 5"

回显(实测):

name = tom
count = 5(Integer)

--count 5 被自动转换为 Integer 并正确打印,方案 A 安装验证通过。也可以不经 Maven 插件、直接用 classpath 运行:

cd /opt/cli-maven && java -Xmx256m -cp target/classes:/root/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar com.example.Main --name tom --count 5

回显(实测):

name = tom
count = 5(Integer)

与 exec:java 结果完全一致。

五、安装方案 B:官方发行包手动安装(下载 jar + java -cp 直跑)

方案 B 不依赖 Maven:从官方站点下载发行包,校验 sha512 后解压取 jar,用 javac/java -cp 直接编译运行,适合无 Maven 的服务器或离线环境。

5.1 下载发行包与校验文件

mkdir -p /opt/downloads && cd /opt/downloads && wget -q https://downloads.apache.org/commons/cli/binaries/commons-cli-1.11.0-bin.tar.gz && wget -q https://downloads.apache.org/commons/cli/binaries/commons-cli-1.11.0-bin.tar.gz.sha512 && ls -l

回显(实测):

total 1680
-rw-r--r-- 1 root root 1713053 Oct 13 06:12 commons-cli-1.11.0-bin.tar.gz
-rw-r--r-- 1 root root     129 Oct 13 06:12 commons-cli-1.11.0-bin.tar.gz.sha512

两个文件均已下载,主包 1713053 字节。

5.2 sha512 完整性校验

官方 .sha512 文件内容为裸哈希值,先查看再校验:

cd /opt/downloads && cat commons-cli-1.11.0-bin.tar.gz.sha512

回显(实测):

dce3ebfb3ba0af9c9d67df7b83ec7348a07d436c47b0be06a1646e19bf652cb4cbe6b6c0b303e4dd0883cf697ca32587108d057b454a9cc8fa6553251114b44f

💡 技术拓展:摘要算法不止一种,常见选择对比——

算法摘要长度十六进制长度碰撞抗性现状适用场景
MD5128 位32 字符已可构造碰撞非安全场景的快速校验、兼容旧协议
SHA-1160 位40 字符已被实质攻破遗留系统兼容(如 git 对象)
SHA-256256 位64 字符目前安全通用完整性校验、数字签名
SHA-512512 位128 字符目前安全高安全要求、大文件校验

Apache 官方发行包现提供 .sha512 校验文件(.asc 为 GPG 签名,需要更高保证时可用),本文遵循官方做法采用 SHA-512。

cd /opt/downloads && sha512sum commons-cli-1.11.0-bin.tar.gz

回显(实测):

dce3ebfb3ba0af9c9d67df7b83ec7348a07d436c47b0be06a1646e19bf652cb4cbe6b6c0b303e4dd0883cf697ca32587108d057b454a9cc8fa6553251114b44f  commons-cli-1.11.0-bin.tar.gz

计算出的哈希与官方 .sha512 文件中的裸哈希逐字符一致,校验通过。更规范的做法是拼成标准格式交给 sha512sum -c 自动比对:

cd /opt/downloads && printf '%s  %s\n' "$(cat commons-cli-1.11.0-bin.tar.gz.sha512)" commons-cli-1.11.0-bin.tar.gz | sha512sum -c -

回显(实测):

commons-cli-1.11.0-bin.tar.gz: OK

输出 OK 即校验通过;若输出 FAILED 说明下载不完整或被篡改,必须重新下载(真实失败样例见第八节 FAQ Q4)。

5.3 解压并取出 jar

cd /opt/downloads && tar xzf commons-cli-1.11.0-bin.tar.gz && ls commons-cli-1.11.0/ && mkdir -p /opt/javac-demo/lib && cp commons-cli-1.11.0/commons-cli-1.11.0.jar /opt/javac-demo/lib/

回显(实测,目录内容):

CONTRIBUTING.md
LICENSE.txt
NOTICE.txt
README.md
RELEASE-NOTES.txt
apidocs
commons-cli-1.11.0-javadoc.jar
commons-cli-1.11.0-sources.jar
commons-cli-1.11.0-test-sources.jar
commons-cli-1.11.0-tests.jar
commons-cli-1.11.0.jar
ls -l /opt/javac-demo/lib/

回显(实测):

total 108
-rw-r--r-- 1 root root 110167 Oct 13 06:12 commons-cli-1.11.0.jar

发行包内含主 jar(110167 字节)、sources、javadoc、tests 与离线 apidocs;运行只需要 commons-cli-1.11.0.jar,已复制到 /opt/javac-demo/lib/。

5.4 javac/java -cp 直接编译运行

写一个验证类覆盖「布尔选项、选项取值、类型转换、裸参数」四个要点,不经 Maven 直接编译。注意 javac 必须带 -encoding UTF-8(CentOS 8 默认 locale 为 C 时 javac 退回 US-ASCII,含中文源码会报 unmappable character):

cd /opt/javac-demo && cat > Demo1Basic.java <<'EOF'
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 Demo1Basic {
    public static void main(String[] args) {
        Options options = new Options();
        options.addOption("v", "verbose", false, "开启详细日志输出");
        options.addOption(Option.builder("n").longOpt("name").hasArg().argName("USER")
                .desc("指定运行用户名").get());
        options.addOption(Option.builder("c").longOpt("count").hasArg().argName("N")
                .type(Integer.class).desc("任务执行次数").get());

        try {
            CommandLine line = new DefaultParser().parse(options, args);
            if (line.hasOption("v")) {
                System.out.println("[1] -v/--verbose 命中,verbose = true");
            }
            if (line.hasOption("n")) {
                System.out.println("[2] -n/--name 命中,name = " + line.getOptionValue("name"));
            }
            if (line.hasOption("c")) {
                Integer count = (Integer) line.getParsedOptionValue("c");
                System.out.println("[3] -c/--count 命中,count = " + count
                        + "(自动转换为 " + count.getClass().getSimpleName() + ")");
            }
            System.out.println("[4] 未匹配任何选项的裸参数 = " + line.getArgList());
        } catch (ParseException e) {
            System.err.println("解析失败:" + e.getClass().getSimpleName() + " -> " + e.getMessage());
            System.exit(2);
        }
    }
}
EOF
export LANG=C.UTF-8 && javac -encoding UTF-8 -cp lib/commons-cli-1.11.0.jar Demo1Basic.java && java -Xmx256m -cp .:lib/commons-cli-1.11.0.jar Demo1Basic -v --name tom --count 3 alpha

回显(实测):

[1] -v/--verbose 命中,verbose = true
[2] -n/--name 命中,name = tom
[3] -c/--count 命中,count = 3(自动转换为 Integer)
[4] 未匹配任何选项的裸参数 = [alpha]

四个输出点全部正确:布尔选项命中、选项取值、Integer.class 自动类型转换、裸参数收集,方案 B 验证通过。

再跑一个互斥组冲突场景验证异常路径。互斥组用 OptionGroup 表达,验证类 Demo4Group.java 同样写在 /opt/javac-demo 下:

cd /opt/javac-demo && cat > Demo4Group.java <<'EOF'
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 Demo4Group {
    public static void main(String[] args) {
        Options options = new Options();
        OptionGroup group = new OptionGroup();
        group.addOption(Option.builder("j").longOpt("json").desc("输出 JSON 格式").build());
        group.addOption(Option.builder("x").longOpt("xml").desc("输出 XML 格式").build());
        group.addOption(Option.builder("y").longOpt("yaml").desc("输出 YAML 格式").build());
        group.setRequired(true);
        options.addOptionGroup(group);
        options.addOption("f", "force", false, "强制覆盖已存在的输出");

        try {
            CommandLine line = new DefaultParser().parse(options, args);
            String format = line.hasOption("j") ? "json"
                    : line.hasOption("x") ? "xml"
                    : line.hasOption("y") ? "yaml" : "(未选择)";
            System.out.println("选中的输出格式:" + format);
            System.out.println("force = " + line.hasOption("f"));
        } catch (AlreadySelectedException e) {
            System.err.println("互斥组冲突:" + e.getMessage());
            System.exit(2);
        } catch (MissingOptionException e) {
            System.err.println("缺少互斥组:" + e.getMessage());
            System.exit(2);
        } catch (ParseException e) {
            System.err.println("解析失败:" + e.getMessage());
            System.exit(2);
        }
    }
}
EOF
export LANG=C.UTF-8 && javac -encoding UTF-8 -cp lib/commons-cli-1.11.0.jar Demo4Group.java && java -Xmx256m -cp .:lib/commons-cli-1.11.0.jar Demo4Group --json --xml; echo "exit=$?"

回显(实测):

Note: Demo4Group.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
互斥组冲突:The option 'x' was specified but an option from this group has already been selected: 'j'
exit=2

编译回显中的两条 Note 来自 Demo4Group 沿用的 1.10 之前写法——Option.builder(...).build() 中的 build() 在 1.11.0 已标记过时,javac 据此提示,编译照常成功、功能不受影响;1.11.0 的新代码建议改用 .get() 收尾(本文 Demo1Basic 即如此)。冲突信息解读:--json 先命中组内的 j,随后 --xml 再选 x 触发 AlreadySelectedException,程序打印前缀「互斥组冲突:」并以 exit=2 退出。

5.5 核对 jar 内容与版本清单

用 jar tf 确认包内条目数与 MANIFEST 中的版本号:

jar tf /opt/javac-demo/lib/commons-cli-1.11.0.jar | wc -l

回显(实测):

73
cd /tmp && jar xf /opt/javac-demo/lib/commons-cli-1.11.0.jar META-INF/MANIFEST.MF && grep -E "Specification-Title|Specification-Version|Implementation-Version|X-Compile-Target-JDK|Multi-Release" META-INF/MANIFEST.MF

回显(实测):

Implementation-Version: 1.11.0
Specification-Title: Apache Commons CLI
Specification-Version: 1.11.0
X-Compile-Target-JDK: 1.8
Multi-Release: true

包内 73 个条目,MANIFEST.MF 确认 Implementation-Version 为 1.11.0、编译目标 Java 8、多版本 jar(Multi-Release)形态,与下载意图一致。

六、方案对比与选择建议

维度方案 A:Maven 依赖方案 B:官方发行包手动安装
依赖来源Maven 中央仓库(自动)downloads.apache.org(手动)
是否需联网需(首次拉取依赖)需下载一次,之后可离线分发
版本管理改 pom.xml 版本号即可手动下载对应版本 jar、自行管理目录
完整性校验依赖 Maven 仓库校验机制需自行 sha512sum -c(官方提供 .sha512)
附带产物仅主 jar主 jar + sources + javadoc + tests + apidocs
适用场景常规 Maven/Gradle 工程无构建工具的服务器、离线内网、源码研读

关键差异与选型建议:工程内使用一律选方案 A,版本升级只需改一个版本号;临时跑个验证、内网离线机器、或需要 sources/javadoc 离线查阅,用方案 B。两方案可以共存,本文环境即同时具备。

七、多版本管理与升级、降级

7.1 Maven 方式的多版本共存

Maven 本地仓库按「groupId/artifactId/version」目录存放,多版本天然共存。实测拉取上一稳定版 1.10.0:

cd /opt/cli-maven && mvn -q dependency:get -Dartifact=commons-cli:commons-cli:1.10.0 && ls /root/.m2/repository/commons-cli/commons-cli/

回显(实测):

1.0
1.10.0
1.11.0

升级或降级只需修改 pom.xml 中 dependency 的 version 值(如 1.11.0 改为 1.10.0),重新 mvn compile 即生效;用 mvn dependency:list 可核对实际解析到的版本:

cd /opt/cli-maven && mvn -B dependency:list | grep commons-cli

回显(实测):

[INFO]    commons-cli:commons-cli:jar:1.11.0:compile

7.2 手动方式的多版本共存

历史版本从 Apache 归档站获取(实测 1.10.0 仍可下载,1615199 字节):

wget -S --spider https://archive.apache.org/dist/commons/cli/binaries/commons-cli-1.10.0-bin.tar.gz 2>&1 | grep -E "HTTP/|Length:" | head -3

回显(实测):

  HTTP/1.1 200 OK
  Content-Length: 1615199
Length: 1615199 (1.5M) [application/x-gzip]

把不同版本的 jar 放入不同目录(如 /opt/javac-demo/lib/1.10.0/、/opt/javac-demo/lib/1.11.0/),运行时用 -cp 指定其一即可切换,建议在类路径中只保留一个 commons-cli 版本,避免类冲突。

7.3 多 JDK 共存与切换

Commons CLI 1.11.0 编译目标为 Java 8,任何 Java 8+ 的 JDK 均可运行。CentOS 8 用 alternatives 管理多 JDK 共存,java 与 javac 是两个独立的组,切换时要分别处理(详见 4.1 节实测)。查看当前指向:

alternatives --display java | head -2

回显(实测):

java - status is manual.
 link currently points to /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java

切换命令汇总(路径以实际安装为准):

alternatives --set java  /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java
alternatives --set javac /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/javac
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk

(三条命令均实测执行成功,回显与 4.1 节一致。)切换 JDK 后建议重跑 5.4 节的 Demo1Basic 做回归验证。

八、常见问题(FAQ)

Q1:dnf install java-17-openjdk-devel 装完后 java -version 显示的还是 1.8.0_312,怎么办? 这是 CentOS 8 的默认行为:maven 等包的依赖链会带入 java-1.8.0-openjdk,且其 alternatives 优先级(1800312)高于 17,/usr/bin/java 因此指向 1.8。按 4.1 节实测执行两步切换:

alternatives --set java  /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java
alternatives --set javac /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/javac

注意 java 与 javac 是两个独立 alternatives 组,只切 java 不切 javac 时 javac -version 仍为 1.8(实测如此)。切换后 java -version 与 javac -version 均为 17.0.1。

Q2:mvn -version 显示 Java 1.8.0_312,怎么让 Maven 用 JDK 17? Maven 启动脚本优先读 JAVA_HOME 环境变量,未设置时自己探测到的运行时可能是 1.8(实测回显见 4.1 节)。修复:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk

实测设置后 mvn -version 的 Java version 行变为 17.0.1, vendor: Red Hat, Inc.。持久化写入 /etc/profile.d/java.sh。本实验中 Commons CLI 示例按 Java 8 字节码编译,即使 Maven 跑在 1.8 上也能正常构建运行,但统一到 17 可避免后续混用不同 JDK 产生的行为差异。

Q3:Maven 拉取依赖很慢或超时怎么办? 本实验在 4.3 节实测首次编译即从中央仓库完整下载了 compiler 插件与 commons-cli 1.11.0 jar(回显见 4.3 节),链路正常。若你的环境拉取缓慢,可改用国内下载源:编辑 ~/.m2/settings.xml 的 <mirrors> 段添加阿里云公共仓库 https://maven.aliyun.com/repository/public(<id> 取 aliyun、<mirrorOf> 取 central),保存后重新 mvn compile。离线机器直接改走方案 B(方案 B 的完整下载校验流程见第五节)。

Q4:sha512 校验输出 FAILED 怎么处理? 真实失败样例(把下载包追加一个字节后校验):

cd /opt/downloads && cp commons-cli-1.11.0-bin.tar.gz bad.tar.gz && printf X >> bad.tar.gz && printf '%s  %s\n' "$(cat commons-cli-1.11.0-bin.tar.gz.sha512)" bad.tar.gz | sha512sum -c -

回显(实测):

bad.tar.gz: FAILED
sha512sum: WARNING: 1 computed checksum did NOT match

出现 FAILED 说明文件内容与官方哈希不一致:先删除坏包,重新用 wget 下载;若反复失败,检查下载链路中是否存在代理改写或磁盘空间不足。校验通过(输出 OK)前不要使用该发行包。

Q5:无 root 权限能装吗? 可以。方案 B 完全不需要 root:发行包解压到用户目录(如 ~/opt/downloads),javac/java 用已安装的任意 JDK(Java 8+)即可;JDK 本身也可下载.tar.gz 版解压到用户目录后配置 PATH 与 JAVA_HOME。方案 A 需要能写 ~/.m2,同样不要求 root,只是 dnf install 安装 Maven/JDK 那一步需要管理员权限(用户目录解压 JDK + Maven 二进制包同样可行)。

九、卸载与清理

按「工程 → 本地仓库 → 系统包」顺序清理。

删除实验工程与发行包目录:

rm -rf /opt/cli-maven /opt/downloads /opt/javac-demo

回显(实测,无输出即成功):

清理 Maven 本地仓库中的 commons-cli 缓存:

rm -rf /root/.m2/repository/commons-cli && ls /root/.m2/repository/commons-cli

回显(实测):

ls: cannot access '/root/.m2/repository/commons-cli': No such file or directory

卸载系统级 Maven 与 JDK(在全新系统上实测):

dnf remove -y maven java-17-openjdk-devel && dnf autoremove -y

输出节选(实测首尾):

Dependencies resolved.

(中段为移除明细,尾部:)

  xorg-x11-fonts-Type1-7.5-19.el8.noarch

Complete!
Nothing to do.
Complete!

验证卸载结果:

java -version 2>&1; mvn -version 2>&1

回显(实测):

bash: java: command not found
bash: mvn: command not found

两条命令均报 command not found,卸载完成。

十、参考链接

  1. Apache Commons CLI 官方下载:https://downloads.apache.org/commons/cli/
  2. Apache Commons CLI 项目主页:https://commons.apache.org/proper/commons-cli/
  3. Maven 中央仓库 commons-cli:https://mvnrepository.com/artifact/commons-cli/commons-cli
  4. Apache 归档站(历史版本):https://archive.apache.org/dist/commons/cli/
  5. CentOS Vault 归档源说明:https://wiki.centos.org/About/Product