CentOS 8 安装 Apache Commons Lang 指南
在 CentOS 8 上从零引入 Apache Commons Lang(commons-lang3)3.21.0,重点处理默认 JDK8 与 JDK17 并存的 JAVA_HOME 切换、invalid flag: --release 排障、Maven 在线依赖与官方二进制包两套方案。
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 的典型状态,也是本文最重要的排障背景。
二、前置条件
在开始安装前,请确认以下条件满足,否则后续步骤会因权限或网络问题中断。
- root 权限或 sudo:安装软件包需
dnf,创建/opt下的工作目录也需要相应权限。 - 网络可达:需要访问
downloads.apache.org(官方二进制包)与repo.maven.apache.org(Maven 中央仓库)。 - 磁盘空间:官方二进制包约 19 MB,加上本地 Maven 仓库缓存建议预留 ≥ 200 MB。
- 纯 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-lang | org.apache.commons:commons-lang3 |
| 顶级包名 | org.apache.commons.lang | org.apache.commons.lang3 |
| 最低 Java | Java 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 即用 |
| 附带产物 | 仅 jar | jar + 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 持久化。
十一、参考链接
- Apache Commons Lang 官方站点:https://commons.apache.org/proper/commons-lang/
- 官方发行下载目录(binaries/、source/):https://downloads.apache.org/commons/lang/
- 历史版本归档目录:https://archive.apache.org/dist/commons/lang/
- Maven 中央仓库坐标页:https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/
- Apache Commons Lang 3.21.0 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/
- Commons Lang 3.21.0 发布说明(官方包内 RELEASE-NOTES.txt 同内容):https://commons.apache.org/proper/commons-lang/changes-report.html