CentOS 8 安装 Apache Commons Lang 指南

在 CentOS 8 上从零引入 Apache Commons Lang(commons-lang3)3.21.0,重点处理默认 JDK8 与 JDK17 并存的 JAVA_HOME 切换、invalid flag: --release 排障、Maven 在线依赖与官方二进制包两套方案。

Apache Commons LangApache Commons Lang2026/10/15

CentOS 8 安装 Apache Commons Lang 指南

目标:在 CentOS 8 上从零搭好 Apache Commons Lang(commons-lang3)3.21.0 的使用环境,重点处理「默认 JDK 8 与 JDK 17 并存」这一 CentOS 8 典型环境问题(JAVA_HOME 切换、invalid flag: --release 排障、$JAVA_HOME/bin/java 运行坑),并完成 Maven 在线依赖与官方二进制包两套方案的实测验证。以下全部命令与回显均已在 CentOS 8(x86_64)实测通过。

一、环境信息

本节先固定本次实测所用软硬件基线,便于读者逐项对照复现。

  • 操作系统:CentOS Linux 8(x86_64)
  • 默认 JDK:java-1.8.0-openjdk 1.8.0_312(Red Hat 构建,PATH 默认指向)
  • 并存 JDK:java-17-openjdk 17.0.1.0.12-2.el8_5
  • 构建工具:Apache Maven 3.5.4(Red Hat 3.5.4-5,系统包仓库默认版本)
  • 目标库:Apache Commons Lang 3.21.0(坐标 org.apache.commons:commons-lang3,2026-09-29 发布)
  • 传递依赖:无(零传递依赖,实测 mvn dependency:tree 确认,见 4.6)
  • Maven 示例工程目录:/opt/lang-demo
  • 官方二进制包工作目录:/opt/lang-jars

先确认系统版本与默认 Java/Maven 基线(注意:默认 java 是 1.8.0_312,JDK 17 需要显式切换,见 4.4):

cat /etc/os-release | grep -E "NAME|VERSION="
java -version 2>&1
mvn -v | head -3

输出:

NAME="CentOS Linux"
VERSION="8"
PRETTY_NAME="CentOS Linux 8"
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)
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

确认系统里两个 JDK 的并存情况:

dnf list installed "java*openjdk*" 2>/dev/null | grep -E "java-1.8.0-openjdk\.|java-17-openjdk\."
javac -version

输出:

java-1.8.0-openjdk.x86_64           1:1.8.0.312.b07-2.el8_5           @AppStream
java-1.8.0-openjdk-headless.x86_64  1:1.8.0.312.b07-2.el8_5           @AppStream
java-17-openjdk.x86_64              1:17.0.1.0.12-2.el8_5             @AppStream
java-17-openjdk-headless.x86_64     1:17.0.1.0.12-2.el8_5             @AppStream
javac 1.8.0_312

解读:java、javac 命令都指向 JDK 8(1.8.0_312);JDK 17 已安装但未被 PATH 与 JAVA_HOME 引用——这是 CentOS 8 的典型状态,也是本文最重要的排障背景。

二、前置条件

在开始安装前,请确认以下条件满足,否则后续步骤会因权限或网络问题中断。

  1. root 权限或 sudo:安装软件包需 dnf,创建 /opt 下的工作目录也需要相应权限。
  2. 网络可达:需要访问 downloads.apache.org(官方二进制包)与 repo.maven.apache.org(Maven 中央仓库)。
  3. 磁盘空间:官方二进制包约 19 MB,加上本地 Maven 仓库缓存建议预留 ≥ 200 MB。
  4. 纯 Java 库:Commons Lang 是纯 Java 库,无本地原生依赖,x86_64 之外的架构同样可用。

验证两个下载源的连通性:

curl -sI https://downloads.apache.org/commons/lang/binaries/ | head -1
curl -sI https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/ | head -1

输出:

HTTP/1.1 200 OK
HTTP/2 200

⚠️ 注意:本文实测环境 JDK 与 Maven 均已预装(如第一章清单所示)。若你的系统缺少组件,用 dnf install -y java-17-openjdk-devel maven 安装,装完以后续各步的实际回显为准。CentOS 8 官方源已 EOL 时需先处理仓库源(如切换到 vault.centos.org),不在本文展开。

三、版本选型说明

Apache Commons Lang 是 Apache 基金会对 java.lang 的增强工具库(字符串、数组、布尔、元组、区间、反射 builder 等)。安装前先确认版本与坐标——Lang 的「坐标陷阱」必须先弄清:2.x 与 3.x 使用完全不同的 Maven 坐标与包名。

查询 Maven 中央仓库的版本元数据:

curl -s https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/maven-metadata.xml | grep -E "latest|release|lastUpdated"

输出:

    <latest>3.21.0</latest>
    <release>3.21.0</release>
    <lastUpdated>20260929180843</lastUpdated>

结论:latest = release = 3.21.0(lastUpdated 20260929180843,即 2026-09-29 发布),3.x 全系共 27 个版本(3.0 → 3.21.0)。

再查询官方发行目录的当前清单:

curl -s https://downloads.apache.org/commons/lang/binaries/ | grep -oE "commons-lang3?-3?\.?6?[^\"<>]*bin\.tar\.gz" | sort -u

输出(HTML 目录页关键文件名,实测去重后):

commons-lang-2.6-bin.tar.gz
commons-lang3-3.21.0-bin.tar.gz

解读:当前最新 3.21.0 与 2.x 末代 2.6 并存在同一 binaries 目录;2.x 老构件文件名前缀为 commons-lang-,3.x 为 commons-lang3-,与 Maven 坐标的断代完全对应。downloads.apache.org 只保留最新发行版与 2.6,历史版本在 archive.apache.org/dist/commons/lang/(实测归档含 2.4~3.21.0 全部 30 个版本的 -bin.tar.gz)。

两代版本对照与选型:

维度commons-lang 2.6(2.x 末代)commons-lang3 3.21.0(当前线)
Maven 坐标commons-lang:commons-langorg.apache.commons:commons-lang3
顶级包名org.apache.commons.langorg.apache.commons.lang3
最低 JavaJava 1.2+(官方 pom 发行描述)Java 8+(官方 pom source/target 1.8,class major 52 实测)
维护状态2013-03 之后无新版,仅存量维护2026-09-29 发布 3.21.0,活跃维护
二进制包文件名前缀commons-lang-commons-lang3-
典型能力基础 StringUtils/ArrayUtils在 2.x 基础上增加 tuple/mutable/builder/Range,随机数 API 重构

关键差异与选型:新项目一律选 commons-lang3 3.21.0;只有维护依赖 2.x 的遗留系统时才继续使用 commons-lang 2.6。两者坐标不同,可以在同一工程内共存(见第八章实测),迁移期可渐进替换。3.21.0 对运行环境只要求 Java 8+,CentOS 8 默认的 JDK 8 理论上可直接使用该库;但本文示例工程按 release 17 编译(演示代码含较新语法),因此需要切换到 JDK 17——这正是 4.4 的起点。

💡 技术拓展:commons-lang 2.6 → commons-lang3 迁移决策——

  • 坐标变化:commons-lang:commons-lang → org.apache.commons:commons-lang3。只改依赖不改包名会继续落在 2.x 上,必须连坐标一起换。
  • 包名变化:org.apache.commons.lang → org.apache.commons.lang3,import 需全量替换;子包结构大体对应(tuple、mutable、builder 等在 3.x 更完整)。
  • 为什么还留在 2.6:唯一合理理由是维护无法升级的老系统。2.6 已于 2013-03 停止演进,不再获得修复;且 2.x 与 3.x 坐标、包名都不同,可以共存渐进迁移(见 8.3 实测),不存在「必须一刀切」的障碍。
  • 注意 API 差异:2.x 的 StringEscapeUtils 在 3.x 已废弃(功能迁往 commons-text);RandomStringUtils/RandomUtils 在 3.21.0 旧静态方法全部废弃(新写法见《Apache Commons Lang 3.21.0 进阶 API 与废弃迁移详解》)。

四、在线安装方案(A:Maven 依赖引入)

官方推荐方式:通过 Maven 引入 org.apache.commons:commons-lang3:3.21.0。本方案在 CentOS 8 上的关键前置是切换 JAVA_HOME 到 JDK 17(4.4 实测演示不切换为什么会失败),本节按「建工程、写代码、踩坑、修复」的实测顺序展开。

4.1 方案说明与适用场景

本方案创建一个最小 Maven 工程引入依赖并完成编译运行验证。适合可联网的开发/构建环境。CentOS 8 上必须先解决 Maven 实际使用的 JDK 版本问题(4.4 有失败实录与修复过程)。

4.2 创建 Maven 工程并引入依赖

创建工程目录与 pom.xml(全文,其中 maven.compiler.release=17 是 4.4 踩坑的主角):

mkdir -p /opt/lang-demo/src/main/java/demo

/opt/lang-demo/pom.xml 全文:

<?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>demo</groupId>
  <artifactId>lang-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.21.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
          <release>17</release>
          <compilerArgs>
            <arg>-Xlint:deprecation</arg>
          </compilerArgs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

逐项说明:

  • project.build.sourceEncoding=UTF-8:源码编码。示例代码含中文注释/字符串,必须显式声明,否则编译器按平台默认编码(POSIX 环境)处理会报 unmappable character。
  • maven.compiler.release=17:用 JDK 17 语义产出字节码;要求 Maven 运行在 JDK 17 上(4.4 的切换正是为此)。
  • dependencies:仅声明 commons-lang3:3.21.0 一项。
  • maven-compiler-plugin 显式固定 3.11.0 并加 -Xlint:deprecation,用于暴露 3.21.0 的废弃 API 告警(4.4 成功段与 FAQ Q5)。

⚠️ 注意:Commons Lang 3.21.0 本身只要求 Java 8+(官方 pom source/target 1.8)。如果你的工程按 release 8 编译,JDK 8 直接可用该库;本文示例按 release 17 编译是为了演示当前主流工程形态,并顺带覆盖 --release 参数的兼容性边界。

4.3 编写最小验证程序

/opt/lang-demo/src/main/java/demo/StringUtilsDemo.java 全文:

package demo;

import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;

public class StringUtilsDemo {
    public static void main(String[] args) {
        System.out.println("== null 安全约定 ==");
        System.out.println("isEmpty(null) = " + StringUtils.isEmpty(null));
        System.out.println("isEmpty(\"\") = " + StringUtils.isEmpty(""));
        System.out.println("isEmpty(\" \") = " + StringUtils.isEmpty(" "));
        System.out.println("isBlank(null) = " + StringUtils.isBlank(null));
        System.out.println("isBlank(\"\") = " + StringUtils.isBlank(""));
        System.out.println("isBlank(\" \") = " + StringUtils.isBlank(" "));
        System.out.println("isNotBlank(\" a \") = " + StringUtils.isNotBlank(" a "));

        System.out.println("== join ==");
        System.out.println("join([a,b,c], '-') = " + StringUtils.join(new String[]{"a", "b", "c"}, '-'));
        System.out.println("join([1,2,3], ',') = " + StringUtils.join(new int[]{1, 2, 3}, ','));

        System.out.println("== split ==");
        String[] parts = StringUtils.split("a,b,,c", ',');
        System.out.println("split(\"a,b,,c\", ',') = " + Arrays.toString(parts));
        System.out.println("split(null, ',') = " + Arrays.toString(StringUtils.split(null, ',')));
        System.out.println("splitPreserveAllTokens(\"a,b,,c\", ',') = " + Arrays.toString(StringUtils.splitPreserveAllTokens("a,b,,c", ',')));

        System.out.println("== abbreviate ==");
        System.out.println("abbreviate(\"abcdefg\", 10) = " + StringUtils.abbreviate("abcdefg", 10));
        System.out.println("abbreviate(\"abcdefg\", 6) = " + StringUtils.abbreviate("abcdefg", 6));
        try {
            StringUtils.abbreviate("abcdefg", 3);
        } catch (IllegalArgumentException e) {
            System.out.println("abbreviate(\"abcdefg\", 3) 抛出 IllegalArgumentException: " + e.getMessage());
        }

        System.out.println("== substringBetween ==");
        System.out.println("substringBetween(\"<a>text</a>\", \"<a>\", \"</a>\") = " + StringUtils.substringBetween("<a>text</a>", "<a>", "</a>"));

        System.out.println("== overlay ==");
        System.out.println("overlay(\"abcdef\", \"***\", 2, 5) = " + StringUtils.overlay("abcdef", "***", 2, 5));

        System.out.println("== left / right / mid ==");
        System.out.println("left(\"abcdef\", 3) = " + StringUtils.left("abcdef", 3));
        System.out.println("right(\"abcdef\", 3) = " + StringUtils.right("abcdef", 3));
        System.out.println("mid(\"abcdef\", 2, 3) = " + StringUtils.mid("abcdef", 2, 3));
        System.out.println("left(null, 3) = " + StringUtils.left(null, 3));
        System.out.println("right(\"abc\", 10) = " + StringUtils.right("abc", 10));

        System.out.println("== capitalize / defaultString ==");
        System.out.println("capitalize(\"hello\") = " + StringUtils.capitalize("hello"));
        System.out.println("capitalize(null) = " + StringUtils.capitalize(null));
        System.out.println("defaultString(null) = [" + StringUtils.defaultString(null) + "]");
        System.out.println("defaultString(null, \"N/A\") = " + StringUtils.defaultString(null, "N/A"));
    }
}

说明:程序覆盖 StringUtils 的 null 安全约定、join/split、缩写、截取与首字母大写等高频用法,作为依赖引入是否成功的真实验证。

4.4 关键前置:切换 JAVA_HOME 到 JDK 17(踩坑实录)

工程与演示代码就绪后,先在默认环境(不设 JAVA_HOME,Maven 落在第一章确认的默认 JDK 8 上)直接编译,实测失败:

cd /opt/lang-demo && mvn -B compile

输出节选(实测尾部):

[INFO] Compiling 6 source files with javac [debug release 17] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.328 s
[INFO] Finished at: 2026-10-14T15:26:46Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project lang-demo: Fatal error compiling: invalid flag: --release -> [Help 1]

原因与要点(本文最重要的一条坑):

  • pom 的 release 17 需要 JDK 9+ 的 javac(--release 参数 JDK 9 才引入,JDK 8 的 javac 不认识,直接报 invalid flag: --release)。
  • mvn -version 显示的 Java version: 才是 Maven 实际使用的 JDK,不是 java -version 的结果。
  • 解决:export JAVA_HOME=/usr/lib/jvm/java-17-openjdk 后重新 mvn compile。
  • 注意:设了 JAVA_HOME 后 PATH 上的 java 仍是 1.8.0_312(第一章回显),运行编译产物必须用 $JAVA_HOME/bin/java(实测见 4.5 与 FAQ Q2)。

JAVA_HOME 的持久化配置见 7.1。现在设置并重新编译(先 mvn -version | head -3 确认 Maven 实际使用的 JDK):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-demo
mvn -version | head -3
mvn -B compile

输出(实测,mvn -version 段与 compile 段、结尾):

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
[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ lang-demo ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 6 source files with javac [debug release 17] to target/classes
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[9,74] nextInt(int,int) in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[10,79] nextDouble(double,double) in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[11,72] nextBoolean() in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[14,90] randomAlphabetic(int) in org.apache.commons.lang3.RandomStringUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[15,87] randomNumeric(int) in org.apache.commons.lang3.RandomStringUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java:[29,33] <T>between(T,T) in org.apache.commons.lang3.Range has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java:[38,34] <T>between(T,T) in org.apache.commons.lang3.Range has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java:[41,34] <T>between(T,T) in org.apache.commons.lang3.Range has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/StringUtilsDemo.java:[54,75] defaultString(java.lang.String,java.lang.String) in org.apache.commons.lang3.StringUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/ExceptionSystemDemo.java:[37,75] <T>defaultIfNull(T,T) in org.apache.commons.lang3.ObjectUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/ExceptionSystemDemo.java:[52,46] getValue() in org.apache.commons.lang3.mutable.MutableInt has been deprecated
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.409 s
[INFO] Finished at: 2026-10-14T15:26:53Z
[INFO] ------------------------------------------------------------------------

说明:实测工程包含 6 个演示类(后续章节与《Apache Commons Lang 3.21.0 常用 API 与工具类详解》《Apache Commons Lang 3.21.0 进阶 API 与废弃迁移详解》两篇的示例同源),读者按本文操作只建 1 个类时显示 Compiling 1 source files 属正常。11 条 WARNING 均为 3.21.0 的废弃 API 告警(-Xlint:deprecation 全量暴露),不影响运行,详见 FAQ Q5。

4.5 运行验证

注意必须用 $JAVA_HOME/bin/java(原因见 4.4 与 FAQ Q2):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-demo
CP=target/classes:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
$JAVA_HOME/bin/java -Dfile.encoding=UTF-8 -cp $CP demo.StringUtilsDemo

输出(实测,与 Ubuntu 22.04 + JDK 17.0.20.1 环境逐行一致):

== null 安全约定 ==
isEmpty(null) = true
isEmpty("") = true
isEmpty(" ") = false
isBlank(null) = true
isBlank("") = true
isNotBlank(" a ") = true
== join ==
join([a,b,c], '-') = a-b-c
join([1,2,3], ',') = 1,2,3
== split ==
split("a,b,,c", ',') = [a, b, c]
split(null, ',') = null
splitPreserveAllTokens("a,b,,c", ',') = [a, b, , c]
== abbreviate ==
abbreviate("abcdefg", 10) = abcdefg
abbreviate("abcdefg", 6) = abc...
abbreviate("abcdefg", 3) 抛出 IllegalArgumentException: Minimum abbreviation width is 4
== substringBetween ==
substringBetween("<a>text</a>", "<a>", "</a>") = text
== overlay ==
overlay("abcdef", "***", 2, 5) = ab***f
== left / right / mid ==
left("abcdef", 3) = abc
right("abcdef", 3) = def
mid("abcdef", 2, 3) = cde
left(null, 3) = null
right("abc", 10) = abc
== capitalize / defaultString ==
capitalize("hello") = Hello
capitalize(null) = null
defaultString(null) = []
defaultString(null, "N/A") = N/A

验证点:输出全部符合 StringUtils 的 null 安全约定(详见《Apache Commons Lang 3.21.0 常用 API 与工具类详解》),依赖已正确引入并可正常运行。

4.6 验证零传递依赖与构件一致性

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-demo
mvn -B dependency:tree 2>/dev/null | grep -E "commons-lang3|BUILD"

输出:

[INFO] \- org.apache.commons:commons-lang3:jar:3.21.0:compile
[INFO] BUILD SUCCESS

结论:依赖树中只有 commons-lang3 自己,零传递依赖,不会拖入任何其他版本族。

对本地仓库中的 jar 计算哈希,稍后与方案 B 解压出的 jar 对比:

sha256sum /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar

输出:

a2af247ec4db6eb8947e18e23e0433e4871a7c5d0a40e1f92e46ede54ae1defe  /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar

五、离线安装方案(B:官方二进制包)

适合内网隔离、无法访问中央仓库的环境。官方 bin 包零依赖,解压取 jar 即用,无需补任何第三方 jar。

5.1 方案说明与适用场景

官方 commons-lang3-3.21.0-bin.tar.gz 内含核心 jar、sources/javadoc/tests jar 与离线 apidocs。由于 3.21.0 零传递依赖,本方案不需要补任何依赖 jar,流程为:下载 → sha512 校验 → 解压 → javac/java -cp 直跑验证。

5.2 下载与校验

mkdir -p /opt/lang-jars && cd /opt/lang-jars
curl -fL -O https://downloads.apache.org/commons/lang/binaries/commons-lang3-3.21.0-bin.tar.gz
curl -fL -sO https://downloads.apache.org/commons/lang/binaries/commons-lang3-3.21.0-bin.tar.gz.sha512
stat -c "%n %s bytes" commons-lang3-3.21.0-bin.tar.gz
sha512sum commons-lang3-3.21.0-bin.tar.gz
cat commons-lang3-3.21.0-bin.tar.gz.sha512

输出(实测,curl 进度为下载全程真实回显):

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.7M  100 18.7M    0     0   870k      0  0:00:22  0:00:22 --:--:--  870k
commons-lang3-3.21.0-bin.tar.gz 19705282 bytes
1c225a75b66946adc007ca774e59671b9c8ac7c0a9f17977829b4530121fadd322ff179a54a9e7d2d4f8bd1095071fe3ee9b8da7902a82351f8b8ce4d530f129  commons-lang3-3.21.0-bin.tar.gz
1c225a75b66946adc007ca774e59671b9c8ac7c0a9f17977829b4530121fadd322ff179a54a9e7d2d4f8bd1095071fe3ee9b8da7902a82351f8b8ce4d530f129

验证点:包体积 19,705,282 字节(18.7M),sha512sum 计算值与官方 .sha512 文件逐字符一致,包完整可信。

5.3 解压与 jar 内容核对

cd /opt/lang-jars && tar xzf commons-lang3-3.21.0-bin.tar.gz && ls commons-lang3-3.21.0/

输出:

apidocs  commons-lang3-3.21.0-javadoc.jar  commons-lang3-3.21.0-sources.jar  commons-lang3-3.21.0-test-sources.jar  commons-lang3-3.21.0-tests.jar  commons-lang3-3.21.0.jar  CONTRIBUTING.md  LICENSE.txt  NOTICE.txt  README.md  RELEASE-NOTES.txt

解读:核心运行 jar 是 commons-lang3-3.21.0.jar;apidocs/ 与 -javadoc.jar 提供离线 API 文档,-sources.jar 可在 IDE 中直接反查源码。

与方案 A 的 Maven Central 构件做 sha256 一致性比对:

cd /opt/lang-jars/commons-lang3-3.21.0
sha256sum commons-lang3-3.21.0.jar /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar

输出:

a2af247ec4db6eb8947e18e23e0433e4871a7c5d0a40e1f92e46ede54ae1defe  commons-lang3-3.21.0.jar
a2af247ec4db6eb8947e18e23e0433e4871a7c5d0a40e1f92e46ede54ae1defe  /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar

结论:官方 tar 包内 jar 与 Maven Central 构件 sha256 逐字符一致,两种方式拿到的是同一个构件。

5.4 手动编译运行验证(系统 JDK 8 与 JDK 17 双路径)

复用 4.3 节的 StringUtilsDemo.java。先看**系统 JDK 8(javac 1.8.0_312)**的直接编译——由于库本身是 Java 8 字节码、该示例源码未用 Java 8 之后语法,可以编译成功:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-jars
cp /opt/lang-demo/src/main/java/demo/StringUtilsDemo.java /opt/lang-jars/StringUtilsDemo.java
mkdir -p bin-classes
javac -encoding UTF-8 -cp commons-lang3-3.21.0/commons-lang3-3.21.0.jar -d bin-classes StringUtilsDemo.java
javap -v -cp bin-classes demo.StringUtilsDemo | grep major

输出:

  major version: 52

用系统 java(1.8.0_312)直接运行:

cd /opt/lang-jars
java -Dfile.encoding=UTF-8 -cp bin-classes:commons-lang3-3.21.0/commons-lang3-3.21.0.jar demo.StringUtilsDemo | head -3

输出:

== null 安全约定 ==
isEmpty(null) = true
isEmpty("") = true

再切回 JDK 17 运行完整验证:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-jars
$JAVA_HOME/bin/java -Dfile.encoding=UTF-8 -cp bin-classes:commons-lang3-3.21.0/commons-lang3-3.21.0.jar demo.StringUtilsDemo

输出(实测,与 4.5 逐行一致):

== null 安全约定 ==
isEmpty(null) = true
isEmpty("") = true
isEmpty(" ") = false
isBlank(null) = true
isBlank("") = true
isNotBlank(" a ") = true
== join ==
join([a,b,c], '-') = a-b-c
join([1,2,3], ',') = 1,2,3
== split ==
split("a,b,,c", ',') = [a, b, c]
split(null, ',') = null
splitPreserveAllTokens("a,b,,c", ',') = [a, b, , c]
== abbreviate ==
abbreviate("abcdefg", 10) = abcdefg
abbreviate("abcdefg", 6) = abc...
abbreviate("abcdefg", 3) 抛出 IllegalArgumentException: Minimum abbreviation width is 4
== substringBetween ==
substringBetween("<a>text</a>", "<a>", "</a>") = text
== overlay ==
overlay("abcdef", "***", 2, 5) = ab***f
== left / right / mid ==
left("abcdef", 3) = abc
right("abcdef", 3) = def
mid("abcdef", 2, 3) = cde
left(null, 3) = null
right("abc", 10) = abc
== capitalize / defaultString ==
capitalize("hello") = Hello
capitalize(null) = null
defaultString(null) = []
defaultString(null, "N/A") = N/A

验证点:库自身是 Java 8 字节码(major 52),系统 JDK 8 与 JDK 17 都能加载运行;但如果示例代码用了 Java 8 之后的语法,JDK 8 的 javac 无法编译。

六、方案对比与选择建议

两种方案各有取舍,本节汇总关键差异并给出选型建议。

维度方案 A:Maven 依赖方案 B:官方二进制包
是否需联网构建时需联网(首次下载约 750 kB)仅下载阶段需联网(19 MB 一次)
安装方式声明坐标自动解析手动下载 + 校验 + 解压
依赖完整性自动管理(零传递依赖)零依赖,取 jar 即用
附带产物仅 jarjar + sources + javadoc + tests + apidocs
升级方式改 pom 版本号重新下载解压
JDK 要求release 17 时须 JAVA_HOME=JDK 17(4.4)库为 Java 8 字节码,JDK 8/17 均可运行
适用场景CI、可联网开发、多模块工程内网隔离、离线运行、快速验证

选择建议:可联网优先方案 A(坐标即版本,升级只改一个数字);内网隔离或临时验证选方案 B(解压即用,且自带全套离线文档)。两者 jar 的 sha256 实测一致,行为完全相同,可按场景互换。

七、基础配置说明

Commons Lang 是进程内库,没有任何配置文件(无 properties/yaml/xml),所有「配置」都集中在编译期编码、运行期 JVM 参数与 JDK 选择三处,本节逐项说明。

7.1 JAVA_HOME 的持久化配置

4.4 的 export JAVA_HOME=/usr/lib/jvm/java-17-openjdk 只对当前会话生效。持久化到 profile 脚本(所有新会话自动生效):

printf "export JAVA_HOME=/usr/lib/jvm/java-17-openjdk\n" > /etc/profile.d/java17.sh
cat /etc/profile.d/java17.sh
source /etc/profile.d/java17.sh
mvn -v | head -3
java -version 2>&1 | head -1

输出(实测):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
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
openjdk version "1.8.0_312"

解读:JAVA_HOME 生效后 mvn -v 显示 Java 17.0.1(Maven 编译用的是它);而 PATH 上的 java 仍指向 1.8.0_312——这是预期行为,运行编译产物时必须用 $JAVA_HOME/bin/java(FAQ Q2 有反例实录)。若想让 java 命令本身也指向 17,可用 alternatives --config java 调整系统默认,本文保持默认不动以贴近典型生产状态。

7.2 编译期编码

  • Maven 工程:由 pom 的 project.build.sourceEncoding=UTF-8 统一控制(4.2 已设置)。
  • javac 手工编译:必须显式加 -encoding UTF-8(5.4 的写法),否则 POSIX locale 下编译含中文源码报 unmappable character。

7.3 运行期编码

本环境默认 locale 为 POSIX,JVM 默认字符集为 ASCII,直接运行含中文 println 的类会输出问号(实录见 FAQ Q3)。解决办法是运行时统一加 -Dfile.encoding=UTF-8:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-demo
CP=target/classes:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
$JAVA_HOME/bin/java -Dfile.encoding=UTF-8 -cp $CP demo.StringUtilsDemo | head -1

输出:

== null 安全约定 ==

本文所有运行命令统一带 -Dfile.encoding=UTF-8。

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

本节覆盖三个真实场景:升级到新版、临时回退旧版、以及多版本共存规则。所有操作均在 CentOS 8 实测。

8.1 升级与降级:只改 pom 版本号

Maven 方式下升级/降级就是改 pom.xml 中 <version> 一行。以「3.21.0 → 3.20.0」的降级为例(升级方向同理)。先确认最小工程在 3.21.0 上的解析(pom 采用 4.2 的依赖声明即可):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-demo
mvn -B dependency:tree 2>/dev/null | grep -E "commons-lang3|BUILD"

输出:

[INFO] \- org.apache.commons:commons-lang3:jar:3.21.0:compile
[INFO] BUILD SUCCESS

临时把 pom 版本改为 3.20.0 并验证——注意 Maven 会在 dependency:tree 时自动从中央仓库下载新版本构件(实测下载了 33 kB 的 pom 与 714 kB 的 jar):

cd /opt/lang-demo
cp pom.xml pom.xml.bak
sed -i "s|<version>3.21.0</version>|<version>3.20.0</version>|" pom.xml
grep -A2 "<artifactId>commons-lang3</artifactId>" pom.xml | grep version
mvn -B dependency:tree 2>&1 | grep -E "Downloaded|commons-lang3|BUILD"
mv pom.xml.bak pom.xml

输出(实测):

      <version>3.20.0</version>
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.20.0/commons-lang3-3.20.0.pom (33 kB at 13 kB/s)
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.20.0/commons-lang3-3.20.0.jar (714 kB at 364 kB/s)
[INFO] \- org.apache.commons:commons-lang3:jar:3.20.0:compile
[INFO] BUILD SUCCESS

验证点:dependency:tree 显示 3.20.0 已生效,且新版本由 Maven 自动下载;最后一条命令已把 pom 恢复为 3.21.0。升级同理——把版本改成目标版本后执行 mvn compile 即可。

8.2 同一 classpath 只能放一个 lang3 版本

3.x 各版本共用同一个 Maven 坐标与同一个包名 org.apache.commons.lang3,同一 classpath 上放两个 3.x 版本时,JVM 按 classpath 顺序取第一个命中者,排序不同结果不同,属未定义用法。实测证明(验证类 ClasspathOrder.java 放在 /opt/lang-jars,全文):

import org.apache.commons.lang3.StringUtils;

/** 打印 classpath 上实际加载到的 commons-lang3 版本 */
public class ClasspathOrder {
    public static void main(String[] args) {
        System.out.println("实际加载的版本 = " + StringUtils.class.getPackage().getImplementationVersion());
    }
}

编译并按两种顺序运行:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-jars
$JAVA_HOME/bin/javac -encoding UTF-8 -cp /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar -d . ClasspathOrder.java
M2=/root/.m2/repository/org/apache/commons/commons-lang3
$JAVA_HOME/bin/java -Dfile.encoding=UTF-8 -cp .:$M2/3.20.0/commons-lang3-3.20.0.jar:$M2/3.21.0/commons-lang3-3.21.0.jar ClasspathOrder
$JAVA_HOME/bin/java -Dfile.encoding=UTF-8 -cp .:$M2/3.21.0/commons-lang3-3.21.0.jar:$M2/3.20.0/commons-lang3-3.20.0.jar ClasspathOrder
ls $M2

输出(实测,前两行分别是两条运行命令的回显,后两行是 ls 回显):

实际加载的版本 = 3.20.0
实际加载的版本 = 3.21.0
3.20.0
3.21.0

结论:classpath 上谁排前谁生效。多版本管理的正确做法是:一个工程(一个 classpath)只声明一个 3.x 版本,用 Maven 的版本号切换;本地仓库可以同时缓存多个版本目录而互不干扰(如上 ls 所示 3.20.0 与 3.21.0 并存)。

8.3 2.x 与 3.x 共存:坐标与包名都不同

2.x 坐标是 commons-lang:commons-lang、包名是 org.apache.commons.lang,与 3.x 完全错开,可以放在同一个 classpath 中共存——这是遗留系统渐进迁移的基础。实测:先下载 2.x 官方二进制包并解压:

cd /opt/lang-jars
curl -fL -sO https://downloads.apache.org/commons/lang/binaries/commons-lang-2.6-bin.tar.gz
ls -l commons-lang-2.6-bin.tar.gz
tar xzf commons-lang-2.6-bin.tar.gz
ls commons-lang-2.6/*.jar

输出(实测):

-rw-r--r-- 1 root root 3418617 Oct 14 16:10 commons-lang-2.6-bin.tar.gz
commons-lang-2.6/commons-lang-2.6-javadoc.jar
commons-lang-2.6/commons-lang-2.6-sources.jar
commons-lang-2.6/commons-lang-2.6.jar

再写一个同时调用两个包名的验证类 CoexistDemo.java(全文):

import org.apache.commons.lang.StringUtils;

/** 2.x 与 3.x 同 classpath 共存:两个包名互不冲突 */
public class CoexistDemo {
    public static void main(String[] args) {
        System.out.println("lang2  isEmpty(\"\") = " + StringUtils.isEmpty(""));
        System.out.println("lang2  version = " + StringUtils.class.getPackage().getImplementationVersion());
        System.out.println("lang3  capitalize = " + org.apache.commons.lang3.StringUtils.capitalize("hello"));
        System.out.println("lang3  version = " + org.apache.commons.lang3.StringUtils.class.getPackage().getImplementationVersion());
    }
}

编译并运行(两个 jar 同时在 classpath 上):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-jars
$JAVA_HOME/bin/javac -encoding UTF-8 -cp commons-lang-2.6/commons-lang-2.6.jar:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar -d . CoexistDemo.java
$JAVA_HOME/bin/java -Dfile.encoding=UTF-8 -cp .:commons-lang-2.6/commons-lang-2.6.jar:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar CoexistDemo

输出(实测):

lang2  isEmpty("") = true
lang2  version = 2.6
lang3  capitalize = Hello
lang3  version = 3.21.0

结论:编译无告警、运行两套 API 各自生效(2.6 与 3.21.0 同时可加载)。迁移期间可让新旧代码并存,逐步把 org.apache.commons.lang 的 import 替换为 org.apache.commons.lang3(注意个别 API 差异,见第三章拓展框)。

九、常见问题(FAQ)

Q1:mvn compile 报 Fatal error compiling: invalid flag: --release 怎么办?

这是 CentOS 8 上最高频的坑:Maven 实际落在默认 JDK 8 上,而 pom 的 --release 17 参数从 JDK 9 才引入 javac,JDK 8 的 javac 不认识该参数。实测报错原文(见 4.4 失败段):

[INFO] Compiling 6 source files with javac [debug release 17] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.328 s
[INFO] Finished at: 2026-10-14T15:26:46Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project lang-demo: Fatal error compiling: invalid flag: --release -> [Help 1]

定位方法:mvn -version | head -3 查看 Java version: 行——它才是 Maven 实际使用的 JDK(本例 JDK 8 时显示 1.8.0_312,切换后显示 17.0.1,见 4.4 两段回显)。解决:export JAVA_HOME=/usr/lib/jvm/java-17-openjdk 后重新编译,或持久化到 /etc/profile.d/java17.sh(7.1)。备选:把工程的 maven.compiler.release 降到与当前 JDK 匹配的值(Commons Lang 本身 Java 8 即可用)。

Q2:明明编译成功了,java -cp target/classes demo.StringUtilsDemo 却报 UnsupportedClassVersionError?

因为 PATH 上的 java 仍是系统默认 JDK 8(第一章回显),而 Maven 按 release 17 编译出的产物是 major 61 字节码,JDK 8 运行时只认到 52。实测复现(用系统 java 跑 4.4 编译的 Maven 产物):

cd /opt/lang-demo
java -cp target/classes:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar demo.StringUtilsDemo 2>&1 | head -4

输出(实测):

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: demo/StringUtilsDemo has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)

解决:运行 Maven 编译产物一律用 $JAVA_HOME/bin/java(4.5 的写法)。两点补充:一是 java -version 显示 1.8.0_312 不代表 JAVA_HOME 没生效(7.1 实测了两者表现);二是若你用系统 JDK 8 的 javac 手工编译且源码无新语法,产物是 major 52,系统 java 可以直接跑(5.4 实测)——但工程化场景建议统一 JDK 17 编译 + $JAVA_HOME/bin/java 运行。

Q3:运行含中文的 println 输出全是问号 ??? 怎么办?

POSIX locale 下 JVM 默认字符集是 ASCII,中文字符编码失败被替换为 ?。实测不加参数直接运行(同 5.4 的 classpath 形态):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk && cd /opt/lang-jars
$JAVA_HOME/bin/java -cp bin-classes:commons-lang3-3.21.0/commons-lang3-3.21.0.jar demo.StringUtilsDemo | head -2

输出节选(实测首部,对比 5.4 正常输出):

== null ???? ==
isEmpty(null) = true

解决:运行时加 -Dfile.encoding=UTF-8(4.5、5.4、7.3 的写法)。该参数只影响运行期输出编码,与编译期编码(7.2)互相独立,两者都要设置。

Q4:编译报 cannot find symbol: class Triple(或其他类)怎么办?

九成是 import 写错了包位置。Triple/Pair 在 org.apache.commons.lang3.tuple 子包(不在 lang3 顶层),MutableInt 在 org.apache.commons.lang3.mutable。定位工具是 jar tf——直接在 jar 里搜类名确认真实包路径:

jar tf /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar | grep -iE "triple|/Pair"

输出:

org/apache/commons/lang3/tuple/ImmutableTriple.class
org/apache/commons/lang3/tuple/MutableTriple.class
org/apache/commons/lang3/tuple/Pair.class
org/apache/commons/lang3/tuple/Triple.class

把 import 改为 org.apache.commons.lang3.tuple.Triple 即可。完整的报错形态与修复实录见《Ubuntu 22.04 安装 Apache Commons Lang 指南》FAQ Q2(同一错误的完整三段报错原文)。

Q5:编译出现大量 has been deprecated WARNING 怎么办?

这是 3.21.0 的废弃 API 告警(4.4 成功段实测共 11 条,全部由 -Xlint:deprecation 暴露),功能仍正常、不影响运行。要点:RandomStringUtils/RandomUtils 的旧静态方法、Range.between(T,T)、StringUtils.defaultString(String,String)、ObjectUtils.defaultIfNull、MutableInt.getValue() 均已 @Deprecated。新写法(如 RandomStringUtils.insecure().nextAlphabetic(8))与逐项迁移表见《Apache Commons Lang 3.21.0 进阶 API 与废弃迁移详解》。

十、卸载与清理

Commons Lang 不做系统级安装,删除工作目录与本地仓库缓存即可完全清理:

rm -rf /opt/lang-demo /opt/lang-jars
ls -d /opt/lang-demo /opt/lang-jars 2>/dev/null || echo "lang-demo lang-jars removed"

输出:

lang-demo lang-jars removed

如需连依赖缓存一起清理:

rm -rf /root/.m2/repository/org/apache/commons/commons-lang3 /root/.m2/repository/org/apache/commons/commons-lang

如需移除 JDK 与 Maven 系统包:dnf remove -y java-17-openjdk maven(注意默认 JDK 8 是系统基础组件,谨慎移除);/etc/profile.d/java17.sh 一并删除即可撤销 JAVA_HOME 持久化。

十一、参考链接

  1. Apache Commons Lang 官方站点:https://commons.apache.org/proper/commons-lang/
  2. 官方发行下载目录(binaries/、source/):https://downloads.apache.org/commons/lang/
  3. 历史版本归档目录:https://archive.apache.org/dist/commons/lang/
  4. Maven 中央仓库坐标页:https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/
  5. Apache Commons Lang 3.21.0 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/
  6. Commons Lang 3.21.0 发布说明(官方包内 RELEASE-NOTES.txt 同内容):https://commons.apache.org/proper/commons-lang/changes-report.html