CentOS 8 安装 Apache Commons IO 指南

在 CentOS 8 上从零安装 Apache Commons IO 2.22.0:dnf 安装 JDK 17 与 Maven 3.5.4、切换默认 JDK、官方 bin 包与 Maven 坐标两种方案、多版本管理与卸载。

Apache Commons IOApache Commons IO2026/10/14

CentOS 8 安装 Apache Commons IO 指南

目标:在 CentOS 8 上从零搭建 Apache Commons IO 2.22.0 的开发与运行环境(JDK 17 + Maven 3.5.4 + 官方依赖),并跑通文件读写、目录遍历、日志追尾与目录监控等核心能力;本文全部命令与回显均已在 CentOS 8 实测通过。

Apache Commons IO 是 Apache Commons 项目下的通用 IO 工具库,提供对 java.nio 与 java.io 的补充封装,屏蔽了流关闭、目录递归、文件名解析、文件追尾等大量样板代码。它是纯 Java 库,没有独立进程与常驻服务,安装的本质就是「装好 JDK/构建工具 → 引入依赖 → 写最小验证代码 → 编译运行」。与 Ubuntu 相比,CentOS 8 有一个必须注意的差异:自带的 Maven 3.5.4 默认绑定 Java 8,需要显式设置 JAVA_HOME 才能用 JDK 17 编译,本文在 4.4 节给出完整的修正方法。

一、环境信息

本节先固定可复现的基线环境,后续所有命令均在此环境执行。列出操作系统、架构、JDK、构建工具与组件版本,便于读者对照复现。

  • 操作系统:CentOS Linux 8(NAME="CentOS Linux",VERSION="8")
  • 架构:x86_64
  • JDK:OpenJDK 17.0.1(dnf 包 java-17-openjdk-devel,版本 1:17.0.1.0.12-2.el8_5.x86_64)
  • 构建工具:Apache Maven 3.5.4(Red Hat 3.5.4-5)
  • 组件版本:commons-io 2.22.0
  • 官方下载目录:https://downloads.apache.org/commons/io/
  • Maven 坐标:commons-io:commons-io:2.22.0
  • 验证用工作目录:/root/commons-io-demo(工程)、/root/io-demo-work(示例读写目录)

系统与架构确认(实时回显):

cat /etc/os-release | head -5
NAME="CentOS Linux"
VERSION="8"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
uname -m
x86_64

💡 说明:Commons IO 是库而非服务,环境中不存在需要常驻的守护进程;「安装完成」的判定标准是可编译并运行一个调用其 API 的最小程序,而不是某个端口处于监听状态。

二、前置条件

本节说明安装前必须满足的条件。CentOS 8 自带可用的 AppStream 软件源,联网即可通过 dnf 获取 JDK 与 Maven。

  1. 具有 root 权限的 CentOS 8 系统。
  2. 可访问 CentOS 官方软件源(dnf 能正常解析与安装软件包)。
  3. 可访问 Apache 官方下载站 downloads.apache.org 与 Maven 中央仓库 repo1.maven.org(本文实测网络直连可达)。
  4. 至少 2 GB 可用磁盘空间(JDK + Maven + 依赖合计约 500 MB)。

网络连通性确认(实时回显):

curl -sI --max-time 20 https://downloads.apache.org/commons/io/ | head -3
HTTP/1.1 200 OK
Date: Tue, 14 Oct 2026 01:40:00 GMT
Server: Apache/2.4.62 (Unix)
curl -sI --max-time 20 https://repo1.maven.org/maven2/commons-io/commons-io/ | head -1
HTTP/1.1 200 OK

三、版本选型说明

本节给出 Commons IO 的版本谱系,帮助读者在 1.x 与 2.x 之间做出选择。截至实测日期,官方下载目录 https://downloads.apache.org/commons/io/ 上的最新稳定版为 2.22.0,binaries/ 目录实测内容如下:

curl -s --max-time 30 https://downloads.apache.org/commons/io/binaries/ | grep -oE "commons-io-[0-9.]+[^\"< ]*" | sort -u
commons-io-2.22.0-bin.tar.gz
commons-io-2.22.0-bin.tar.gz.asc
commons-io-2.22.0-bin.tar.gz.sha512
commons-io-2.22.0-bin.zip
commons-io-2.22.0-bin.zip.asc
commons-io-2.22.0-bin.zip.sha512
版本线最新版JDK 基线命名空间/坐标适用场景
1.x(已停止维护)1.4Java 1.3+commons-io:commons-io:1.4极老项目兼容,不建议新用
2.x(当前主线)2.22.0Java 8+commons-io:commons-io:2.x所有新项目默认选择
3.x(尚未发布)未发布Java 8+未定关注后续动态

关键差异与选型:2.x 修正了 1.x 中大量已废弃 API(如 FileUtils、IOUtils 的旧签名),并以 Java 8 为最低基线;实测 2.22.0 的字节码主版本号为 52(即 Java 8),因此 JDK 8 及以上均可直接使用,本文采用 JDK 17 作为运行与构建环境。本文实际采用 2.22.0,理由是它为官方下载站当前最新稳定版,且对 JDK 17 完全兼容。

版本确认为 2.22.0 的实测回显:

curl -s --max-time 30 https://downloads.apache.org/commons/io/ | grep -oE "2\.[0-9]+\.[0-9]+" | sort -u | tail -1
2.22.0

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

本节面向常规 Java 项目开发,通过 dnf 安装 JDK 与 Maven,再用 Maven 坐标引入 commons-io 依赖并跑通最小验证程序。这是最贴合工程实践的方式,也是 CentOS 8 上唯一需要处理 JDK 绑定问题的方式。

4.1 方案说明

方案 A 的链路是:dnf 安装 java-17-openjdk-devel 与 maven → 显式设置 JAVA_HOME 指向 JDK 17 → 在 pom.xml 中声明 commons-io:commons-io:2.22.0 → Maven 自动从中央仓库下载 jar 到本地仓库 → 编译并运行验证程序。优点是版本由 pom 统一管控、依赖自动解析;缺点是首次构建需要联网,且必须先处理 4.4 节的 JDK 绑定问题。

4.2 安装 JDK 17

CentOS 8 AppStream 源内置 java-17-openjdk-devel,直接安装即可:

dnf install -y java-17-openjdk-devel
Package java-17-openjdk-devel-1:17.0.1.0.12-2.el8_5.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!

安装后确认版本与系统默认 java 的指向:

java -version
openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment 21.9 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM 21.9 (build 17.0.1+12-LTS, mixed mode, sharing)
alternatives --display java | head -3
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

验证点:java -version 输出 17.0.1,且 alternatives 的 link currently points to 指向 JDK 17 的可执行文件,说明命令行 java 已就绪。

4.3 安装 Maven

dnf install -y maven
Package maven-1:3.5.4-5.module_el8.0.0+39+6a9b6e22.noarch is already installed.
Dependencies resolved.
Nothing to do.
Complete!

CentOS 8 通过 dnf 模块(module)提供 Maven,确认可选模块流:

dnf module list maven | tail -6
CentOS Linux 8 - AppStream
Name  Stream     Profiles   Summary
maven 3.5 [d][e] common [d] Java project management and project comprehension tool
maven 3.6        common [d] Java project management and project comprehension tool

要点:默认启用([d][e])的是 maven 3.5 流,即 Maven 3.5.4;如需 3.6 可 dnf module enable maven:3.6 后重装,但本文按默认流 3.5.4 实测(3.5.4 配合下文 4.5 节的插件选择完全够用)。

4.4 关键差异:Maven 默认绑定 Java 8,需显式指定 JAVA_HOME

这是 CentOS 8 与 Ubuntu 的最大差异。尽管命令行 java 已指向 17,Maven 3.5.4 的启动脚本会通过 /usr/share/java-utils/java-functions 中的 set_jvm 逻辑选择默认 JVM,未设置 JAVA_HOME 时取到的是 Java 8 的 JRE。先确认默认绑定(实时回显):

mvn -version
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
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"

Java version 一行显示 1.8.0_312,说明 Maven 正运行在 Java 8 上。此时若直接编译 source/target = 17 的工程会失败,实测报错(干净重建):

cd /root/commons-io-demo && rm -rf target && mvn -B package 2>&1 | tail -6
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.646 s
[INFO] Finished at: 2026-10-13T18:09:29Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project commons-io-demo: Fatal error compiling: invalid target release: 17 -> [Help 1]

修正方法:显式导出 JAVA_HOME 指向 JDK 17 安装目录(该路径即 4.2 节 alternatives 显示的实际路径):

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

再次确认 Maven 绑定的 JDK(实时回显):

mvn -version
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_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"

验证点:Java version 变为 17.0.1。注意该 export 只对当前会话生效,重启会话或新开终端后需重新执行,或把 export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64 写入 ~/.bashrc 持久化。后续小节的所有 Maven 命令都在已导出 JAVA_HOME 的会话中执行。

4.5 创建工程并引入依赖

建立一个标准 Maven 工程,pom.xml 中声明 commons-io 依赖,并用 maven-shade-plugin 打成可直接运行的胖 jar(内嵌 commons-io,免去运行时手工拼 classpath)。

mkdir -p /root/commons-io-demo/src/main/java
cd /root/commons-io-demo

pom.xml(完整内容,文件路径 /root/commons-io-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>com.example.io</groupId>
  <artifactId>commons-io-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

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

  <dependencies>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.22.0</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>commons-io-demo</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>Demo</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

逐项说明关键配置:

  • commons-io:commons-io:2.22.0:唯一运行期依赖,Maven 会自动下载并缓存到 ~/.m2/repository/commons-io/commons-io/2.22.0/。
  • maven.compiler.source/target=17:按 JDK 17 编译;若目标机器是 JDK 8,可改回 1.8(2.22.0 字节码基线即 Java 8)。
  • maven-shade-plugin:把 commons-io 的类打进最终 jar,Main-Class 设为 Demo,从而 java -jar 可独立运行。
  • finalName:固定产物名称为 commons-io-demo.jar,避免带版本号后缀,方便脚本引用。

⚠️ 注意:maven-jar-plugin 3.4.x 要求 Maven 3.6.3+,而 CentOS 8 自带 Maven 3.5.4,直接使用会报 requires Maven version 3.6.3。因此本文统一使用 maven-shade-plugin 3.5.1(要求 Maven 3.2.5+),实测在 3.5.4 上可正常完成打包。

4.6 最小验证代码

验证程序 Demo.java(文件路径 /root/commons-io-demo/src/main/java/Demo.java)同时覆盖官方文档中的多个核心工具类,并读写真实文件与目录。完整源码与 Ubuntu 篇一致,此处给出与编译运行直接相关的要点:它依次演示 FileUtils 文件读写、IOUtils 流复制、FilenameUtils 文件名解析、FileUtils 目录遍历、Tailer 日志追尾与 FileAlterationMonitor 目录监控六组能力。

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListener;
import org.apache.commons.io.input.TailerListenerAdapter;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

public class Demo {

    static final File BASE = new File("/root/io-demo-work");

    public static void main(String[] args) throws Exception {
        FileUtils.deleteDirectory(BASE);
        FileUtils.forceMkdir(BASE);
        System.out.println("工作目录: " + BASE + " 存在=" + BASE.exists());

        demoFileUtils();
        demoIOUtils();
        demoFilenameUtils();
        demoWalk();
        demoTailer();
        demoMonitor();
    }

    static void demoFileUtils() throws Exception {
        File readme = new File(BASE, "readme.txt");
        FileUtils.writeStringToFile(readme, "Hello Commons IO\n第二行 中文\n第三行\n", StandardCharsets.UTF_8);
        System.out.println("---- [1] FileUtils.writeStringToFile / readFileToString ----");
        System.out.print(FileUtils.readFileToString(readme, StandardCharsets.UTF_8));
        System.out.println("文件字节数=" + readme.length() + ", 文本行数=" + FileUtils.readLines(readme, StandardCharsets.UTF_8).size());
    }

    static void demoIOUtils() throws Exception {
        File src = new File(BASE, "readme.txt");
        File dst = new File(BASE, "readme-copy.txt");
        System.out.println("---- [2] IOUtils.copy / copyLarge / toString ----");
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dst)) {
            System.out.println("IOUtils.copy 传输字节数=" + IOUtils.copy(in, out));
        }
        String a = IOUtils.toString(new FileInputStream(src), StandardCharsets.UTF_8);
        String b = IOUtils.toString(new FileInputStream(dst), StandardCharsets.UTF_8);
        System.out.println("复制后内容一致=" + a.equals(b));

        byte[] buf = new byte[4096];
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(new File(BASE, "readme-copy2.txt"))) {
            System.out.println("IOUtils.copyLarge 缓冲区=4096 传输字节数=" + IOUtils.copyLarge(in, out, buf));
        }
    }

    static void demoFilenameUtils() {
        System.out.println("---- [3] FilenameUtils ----");
        System.out.println("getExtension(/opt/app/logs/app-2026.log) = " + FilenameUtils.getExtension("/opt/app/logs/app-2026.log"));
        System.out.println("getBaseName(/opt/app/logs/app-2026.log)   = " + FilenameUtils.getBaseName("/opt/app/logs/app-2026.log"));
        System.out.println("normalize(/opt/app/../var//log/./x.txt)   = " + FilenameUtils.normalize("/opt/app/../var//log/./x.txt"));
        System.out.println("getFullPath(/opt/data/quzuo/docs/a.md)    = " + FilenameUtils.getFullPath("/opt/data/quzuo/docs/a.md"));
        System.out.println("isExtension(archive.tar.gz, gz)           = " + FilenameUtils.isExtension("archive.tar.gz", "gz"));
    }

    static void demoWalk() throws Exception {
        System.out.println("---- [4] FileUtils 目录遍历 / 复制 / 清理 ----");
        FileUtils.forceMkdir(new File(BASE, "sub/deep"));
        FileUtils.writeStringToFile(new File(BASE, "sub/a.log"), "a\n", StandardCharsets.UTF_8);
        FileUtils.writeStringToFile(new File(BASE, "sub/deep/b.log"), "b\n", StandardCharsets.UTF_8);
        FileUtils.writeStringToFile(new File(BASE, "sub/deep/c.txt"), "c\n", StandardCharsets.UTF_8);

        Iterator<File> it = FileUtils.iterateFiles(new File(BASE, "sub"), new String[]{"log"}, true);
        List<String> hit = new ArrayList<>();
        while (it.hasNext()) {
            hit.add(it.next().getAbsolutePath());
        }
        System.out.println("iterateFiles(递归, 后缀log) 命中 " + hit.size() + " 个:");
        hit.forEach(p -> System.out.println("  " + p));

        try (Stream<File> st = FileUtils.streamFiles(new File(BASE, "sub"), true, "log")) {
            System.out.println("streamFiles(递归, 后缀log) 流计数=" + st.count());
        }

        File treeCopy = new File(BASE, "sub-copy");
        FileUtils.copyDirectory(new File(BASE, "sub"), treeCopy);
        System.out.println("copyDirectory 后 sub-copy 目录大小(字节)=" + FileUtils.sizeOfDirectory(treeCopy)
                + ", 递归文件数=" + FileUtils.listFiles(treeCopy, null, true).size());

        FileUtils.cleanDirectory(treeCopy);
        System.out.println("cleanDirectory 后 sub-copy 内部条目数=" + treeCopy.list().length
                + ", 目录本身仍存在=" + treeCopy.isDirectory());
    }

    static void demoTailer() throws Exception {
        System.out.println("---- [5] Tailer 追尾日志 ----");
        File log = new File(BASE, "app.log");
        FileUtils.writeStringToFile(log, "2026-10-14 10:00:00 INFO 启动\n", StandardCharsets.UTF_8);

        List<String> captured = new ArrayList<>();
        TailerListener listener = new TailerListenerAdapter() {
            @Override
            public void handle(String line) {
                captured.add(line);
                System.out.println("  Tailer 捕获: " + line);
            }
        };
        // 延时 200ms 轮询一次,从文件开头开始跟随;create() 内部已提交后台线程执行
        Tailer tailer = Tailer.create(log, listener, 200L, false);
        Thread.sleep(600L);

        FileUtils.writeStringToFile(log, "2026-10-14 10:00:05 INFO 追加一行\n", StandardCharsets.UTF_8, true);
        FileUtils.writeStringToFile(log, "2026-10-14 10:00:10 ERROR 追加错误行\n", StandardCharsets.UTF_8, true);
        Thread.sleep(1200L);
        System.out.println("  Tailer 累计捕获行数=" + captured.size());
        tailer.stop();
    }

    static void demoMonitor() throws Exception {
        System.out.println("---- [6] FileAlterationMonitor 目录变更监控 ----");
        final CountDownLatch latch = new CountDownLatch(3);
        FileAlterationObserver observer = new FileAlterationObserver(BASE);
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileCreate(File file) {
                System.out.println("  [监控] 创建: " + file.getName());
                latch.countDown();
            }

            @Override
            public void onFileChange(File file) {
                System.out.println("  [监控] 修改: " + file.getName());
                latch.countDown();
            }

            @Override
            public void onFileDelete(File file) {
                System.out.println("  [监控] 删除: " + file.getName());
                latch.countDown();
            }
        });
        // 每隔 400ms 扫描一次
        FileAlterationMonitor monitor = new FileAlterationMonitor(400L, observer);
        monitor.start();

        File watch = new File(BASE, "watch-target.txt");
        FileUtils.writeStringToFile(watch, "v1\n", StandardCharsets.UTF_8);
        Thread.sleep(900L);
        FileUtils.writeStringToFile(watch, "v2\n", StandardCharsets.UTF_8, true);
        Thread.sleep(900L);
        FileUtils.deleteQuietly(watch);

        boolean ok = latch.await(5, TimeUnit.SECONDS);
        System.out.println("  监控事件全部到达=" + ok);
        monitor.stop();
    }
}

💡 技术拓展:Java 生态里做「文件/流工具」的库不止 Commons IO 一家,常见方案对比如下——

方案定位特点适用场景
java.nio(JDK 原生)基础 API无第三方依赖,Files/Paths 覆盖大部分场景简单读写、追求零依赖
Commons IO通用 IO 工具集IOUtils/FileUtils/FilenameUtils/Tailer/Monitor 齐全日志追尾、目录监控、批量文件操作
Guava IOGoogle 工具库子模块与 Guava 全家桶耦合,API 偏轻已用 Guava 的工程
OkioKotlin/流式 IO面向现代流与 Buffer,Square 系网络/序列化底层流处理

本文采用 Commons IO,理由是它对「文件树遍历、目录变更监控、日志追尾」这类运维/工程常见需求提供了开箱即用的封装(FileUtils、FileAlterationMonitor、Tailer),且与 JDK 8+ 完全兼容、无额外交付负担。

4.7 编译与运行验证

在已导出 JAVA_HOME 的会话中执行 Maven 打包:

cd /root/commons-io-demo && mvn -B package
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /root/commons-io-demo/target/commons-io-demo.jar with /root/commons-io-demo/target/commons-io-demo-1.0.0-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.779 s
[INFO] Finished at: 2026-10-13T18:09:48Z
[INFO] ------------------------------------------------------------------------
ls -l target/*.jar
-rw-r--r-- 1 root root 622884 Oct 13 18:09 target/commons-io-demo.jar
-rw-r--r-- 1 root root  14865 Oct 13 18:09 target/original-commons-io-demo.jar

commons-io-demo.jar(约 623 KB)是内嵌 commons-io 的胖 jar,original-commons-io-demo.jar 是不含依赖的原始 jar。

运行并观察完整回显:

java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -jar target/commons-io-demo.jar
工作目录: /root/io-demo-work 存在=true
---- [1] FileUtils.writeStringToFile / readFileToString ----
Hello Commons IO
第二行 中文
第三行
文件字节数=44, 文本行数=3
---- [2] IOUtils.copy / copyLarge / toString ----
IOUtils.copy 传输字节数=44
复制后内容一致=true
IOUtils.copyLarge 缓冲区=4096 传输字节数=44
---- [3] FilenameUtils ----
getExtension(/opt/app/logs/app-2026.log) = log
getBaseName(/opt/app/logs/app-2026.log)   = app-2026
normalize(/opt/app/../var//log/./x.txt)   = /opt/var/log/x.txt
getFullPath(/opt/data/quzuo/docs/a.md)    = /opt/data/quzuo/docs/
isExtension(archive.tar.gz, gz)           = true
---- [4] FileUtils 目录遍历 / 复制 / 清理 ----
iterateFiles(递归, 后缀log) 命中 2 个:
  /root/io-demo-work/sub/a.log
  /root/io-demo-work/sub/deep/b.log
streamFiles(递归, 后缀log) 流计数=2
copyDirectory 后 sub-copy 目录大小(字节)=6, 递归文件数=3
cleanDirectory 后 sub-copy 内部条目数=0, 目录本身仍存在=true
---- [5] Tailer 追尾日志 ----
  Tailer 捕获: 2026-10-14 10:00:00 INFO 启动
  Tailer 捕获: 2026-10-14 10:00:05 INFO 追加一行
  Tailer 捕获: 2026-10-14 10:00:10 ERROR 追加错误行
  Tailer 累计捕获行数=3
---- [6] FileAlterationMonitor 目录变更监控 ----
  [监控] 创建: watch-target.txt
  [监控] 修改: watch-target.txt
  [监控] 删除: watch-target.txt
  监控事件全部到达=true

结果解读:六组演示全部按预期输出——文件读写与复制字节数一致(44 字节)、文件名规范化把 /opt/app/../var//log/./x.txt 还原为 /opt/var/log/x.txt、Tailer 捕获到追加的日志行、FileAlterationMonitor 捕获到创建/修改/删除三类事件。这表示 commons-io 2.22.0 已在 CentOS 8 的 JDK 17 上完整可用。

💡 说明:-Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 用于固定中文输出的字符集。CentOS 8 默认 platform encoding: ANSI_X3.4-1968(即 POSIX),省略该参数时中文输出会乱码。

五、方案 B:官方 bin 包 + jar 直跑(离线友好)

本节面向没有 Maven/构建体系的场景,直接使用官方发布的 jar 包,用 javac + java 完成编译运行,不依赖任何构建工具。该方案不受 4.4 节 Maven 绑定 JDK 问题影响,因为全程使用 javac/java 直跑。

5.1 方案说明

链路是:从 downloads.apache.org 下载官方 bin 包 → 校验 sha512 → 解压得到 commons-io-2.22.0.jar → 用 javac -cp 编译源码 → 用 java -cp 运行。适用于离线环境、脚本化部署或纯粹想「拿到 jar 就能用」的读者。

5.2 下载与完整性校验

cd /root && curl -sO --max-time 120 https://downloads.apache.org/commons/io/binaries/commons-io-2.22.0-bin.tar.gz
curl -sO --max-time 60 https://downloads.apache.org/commons/io/binaries/commons-io-2.22.0-bin.tar.gz.sha512
ls -l commons-io-2.22.0-bin.tar.gz
-rw-r--r-- 1 root root 9027936 Oct 13 18:13 commons-io-2.22.0-bin.tar.gz

校验 sha512(期望值与实测值必须逐字一致):

echo "期望: $(cat commons-io-2.22.0-bin.tar.gz.sha512)"
echo "实测: $(sha512sum commons-io-2.22.0-bin.tar.gz | cut -d' ' -f1)"
期望: aa8184f097ee9d43b2fbe56a82ca1ed3f0f4c153687cc18c4d218601948270a92a69c36224317db236715064946a3799727de5c3e57c536ddbaf61a98d84c737
实测: aa8184f097ee9d43b2fbe56a82ca1ed3f0f4c153687cc18c4d218601948270a92a69c36224317db236715064946a3799727de5c3e57c536ddbaf61a98d84c737

⚠️ 注意:若 sha512 不一致,说明下载包损坏或被篡改,须删除后重新下载;切勿跳过校验直接使用。

5.3 解压

tar xzf commons-io-2.22.0-bin.tar.gz && ls commons-io-2.22.0/
LICENSE.txt
NOTICE.txt
RELEASE-NOTES.txt
commons-io-2.22.0-javadoc.jar
commons-io-2.22.0-sources.jar
commons-io-2.22.0-test-sources.jar
commons-io-2.22.0-tests.jar
commons-io-2.22.0.jar
docs

主 jar 为 commons-io-2.22.0.jar(609182 字节,实测):

ls -l commons-io-2.22.0/commons-io-2.22.0.jar
-rw-r--r-- 1 root root 609182 Apr 19 11:39 commons-io-2.22.0/commons-io-2.22.0.jar

5.4 javac 编译与 java 运行

用官方 jar 作为编译与运行期 classpath:

mkdir -p /root/jar-run && cd /root/jar-run
cp /root/commons-io-2.22.0/commons-io-2.22.0.jar .
cp /root/commons-io-demo/src/main/java/Demo.java .
javac -encoding UTF-8 -cp commons-io-2.22.0.jar -d out Demo.java
Note: Demo.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
java -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8 -cp out:commons-io-2.22.0.jar Demo | head -12
工作目录: /root/io-demo-work 存在=true
---- [1] FileUtils.writeStringToFile / readFileToString ----
Hello Commons IO
第二行 中文
第三行
文件字节数=44, 文本行数=3
---- [2] IOUtils.copy / copyLarge / toString ----
IOUtils.copy 传输字节数=44
复制后内容一致=true
IOUtils.copyLarge 缓冲区=4096 传输字节数=44
---- [3] FilenameUtils ----
getExtension(/opt/app/logs/app-2026.log) = log

验证点:java -cp 一行成功打印六组结果(此处节选前三组,后三组与 4.7 节完全一致),且无 NoClassDefFoundError,说明 jar 直跑可用;出现 deprecated API 提示属正常(Tailer.create 已在 2.22.0 标记为过时,推荐 Tailer.builder(),但功能完全可用)。

5.5 Maven 中央仓库 jar 的独立获取(可选)

若只想从 Maven 中央仓库获取单个 jar 而不使用构建工具,可直接下载并校验 sha1:

cd /root/jar-run && curl -sO --max-time 60 https://repo1.maven.org/maven2/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar
curl -sO --max-time 30 https://repo1.maven.org/maven2/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar.sha1
echo "期望: $(cat commons-io-2.22.0.jar.sha1)"
echo "实测: $(sha1sum commons-io-2.22.0.jar | cut -d' ' -f1)"
期望: 5b1e18bc0ad651ed878029d83f88d9c8189fd51e
实测: 5b1e18bc0ad651ed878029d83f88d9c8189fd51e

六、方案对比与选择建议

本节汇总两种方案的差异,给出选型建议。两种方案产出的是同一份 commons-io 2.22.0 代码,差别仅在获取与装配方式;方案 B 因全程用 javac/java 直跑,天然绕开了方案 A 在 CentOS 8 上必须处理的 JAVA_HOME 绑定问题。

维度方案 A:Maven 依赖方案 B:官方 bin 包 + jar 直跑
运行期依赖Maven 自动解析手工把 jar 放入 classpath
是否联网首次构建需联网仅下载时需要联网
依赖版本管控pom 统一声明,可锁版本无,靠文件命名区分
多依赖协同(传递依赖)自动处理需手工收集
可复现产物mvn package 产出胖 jarjavac 产出 class + 引用 jar
CentOS 8 特殊处理需先设置 JAVA_HOME 指向 JDK 17无需额外处理
适用场景常规工程开发离线脚本、临时验证、无构建体系

选型建议:若项目有构建体系(Maven/Gradle),一律选方案 A,但 CentOS 8 上务必先按 4.4 节设置 JAVA_HOME;若在无构建工具的老旧服务器上临时验证,或需要把 jar 手工嵌入既有脚本,选方案 B。两者也可共存——用方案 A 开发、用方案 B 分发 jar。

七、配置说明

本节说明 Commons IO 使用中真正需要设置的「配置」。它是库而非服务,没有独立配置文件,全部行为通过代码参数或 JDK 环境变量控制。

环境变量 JAVA_HOME(CentOS 8 上 Maven 依赖它定位 JDK 17,这是与 Ubuntu 最大的配置差异):

echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64 && echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64

逐项说明:

  • JAVA_HOME:Maven 定位 JDK 的优先来源。CentOS 8 的 Maven 3.5.4 启动脚本在 JAVA_HOME 为空时会通过 set_jvm 逻辑取到 Java 8 的 JRE(见 4.4 节),导致编译 source/target = 17 的工程报 invalid target release: 17;显式导出该变量指向 JDK 17 后问题消失。建议写入 ~/.bashrc 持久化。
  • 代码级「参数」:文件读写字符集(如 StandardCharsets.UTF_8)、缓冲区大小(IOUtils.DEFAULT_BUFFER_SIZE 实测为 8192 字节)、Tailer 轮询延时(默认 1000 ms)、FileAlterationMonitor 扫描间隔(默认 10000 ms)等,均通过 API 入参控制,无外部配置文件。各参数的完整默认值见《Apache Commons IO 配置参数详解》。

八、多版本管理

多版本共存靠「隔离依赖坐标 + 隔离 jar 文件名」实现,无需切换全局软链。

Maven 本地仓库中不同版本的 commons-io 并列存放,互不覆盖(实时回显):

ls -1 $HOME/.m2/repository/commons-io/commons-io/
1.4
2.13.0
2.22.0
2.6

要点:工程要用哪个版本,只在 pom.xml 的 <dependency><version> 中改数字即可,其余版本仍留在本地仓库供其他工程复用。jar 直跑场景,把不同版本的 jar(如 commons-io-2.13.0.jar)分目录存放,运行时按需拼 classpath。

切换工程依赖版本并验证生效:

cd /root/commons-io-demo && mvn -q -B dependency:tree | grep commons-io
[INFO] \- commons-io:commons-io:jar:2.22.0:compile

九、常见问题(FAQ)

Q1:mvn -B package 报 invalid target release: 17 怎么办? 这是 Maven 正运行在 Java 8 上导致的(mvn -version 的 Java version 一行为 1.8.0_312)。解决:export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64 后重新执行;需要持久化则写入 ~/.bashrc。

Q2:mvn -version 显示的 Java version 是 1.8,但 java -version 是 17? 正常现象。CentOS 8 的 Maven 3.5.4 通过 /usr/share/java-utils/java-functions 的 set_jvm 逻辑选 JVM,不读 PATH 中的 java。按 Q1 的方式显式设置 JAVA_HOME 即可切换。

Q3:mvn package 报 maven-jar-plugin:3.4.x requires Maven version 3.6.3 怎么办? 插件版本高于本机 Maven 版本导致。CentOS 8 自带 Maven 3.5.4,无法满足 3.6.3+ 要求。两种解法:一是 dnf module enable maven:3.6 后升级 Maven;二是改用兼容低版本 Maven 的插件。本文 pom.xml 已统一使用 maven-shade-plugin 3.5.1(要求 Maven 3.2.5+),实测可避免该问题。

Q4:运行时报 Could not find or load main class Demo? 说明运行期 classpath 未包含编译产物或 commons-io jar。jar 直跑时确认命令为 java -cp out:commons-io-2.22.0.jar Demo(out 为 javac -d 指定的输出目录);java -jar 方式则要确保已用 shade 打包出含 Demo.class 的胖 jar。

Q5:中文输出显示为乱码(问号)? CentOS 8 默认 platform encoding: ANSI_X3.4-1968(POSIX),JVM 会用 ASCII 输出中文。解决:运行时加 -Dstdout.encoding=UTF-8 -Dfile.encoding=UTF-8,或把系统 locale 调成 UTF-8。

Q6:commons-io 2.22.0 能不能跑在 JDK 8 上? 可以。实测其字节码主版本号为 52(Java 8),JDK 8 及以上均可运行;本文用 JDK 17 属于更高基线,兼容性不受影响。

十、卸载与清理

Commons IO 本身不产生系统级安装,卸载即移除依赖与本地文件。

移除 Maven 工程、jar 直跑目录与下载包(清理某工程独有依赖):

rm -rf /root/commons-io-demo /root/jar-run /root/io-demo-work /root/commons-io-2.22.0 /root/commons-io-2.22.0-bin.tar.gz
ls /root/commons-io-demo 2>&1
ls: cannot access '/root/commons-io-demo': No such file or directory

移除 Maven 本地仓库中的 commons-io(影响所有工程,慎用):

rm -rf $HOME/.m2/repository/commons-io/commons-io/2.22.0

卸载 JDK 与 Maven(仅当不再需要)。先以 --assumeno 预演确认将删除的包(Maven 会连带移除大量依赖,本文实测共 135 个包、释放约 464 MB),无误后去掉该选项真正执行:

dnf remove --assumeno java-17-openjdk-devel maven
Transaction Summary
==================================================================================================================
Remove  135 Packages

Freed space: 464 M
Operation aborted.

确认无误后真正卸载(实际生产环境执行):

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

十一、参考链接

  1. Apache Commons IO 官方主页:https://commons.apache.org/proper/commons-io/
  2. Apache Commons IO 官方下载目录:https://downloads.apache.org/commons/io/
  3. Commons IO 2.22.0 发布说明:https://downloads.apache.org/commons/io/RELEASE-NOTES.txt
  4. Commons IO Javadoc(IOUtils/FileUtils/FilenameUtils/Tailer):https://commons.apache.org/proper/commons-io/apidocs/
  5. Maven 中央仓库 commons-io 坐标:https://repo1.maven.org/maven2/commons-io/commons-io/
  6. OpenJDK 17 安装包(CentOS 8 AppStream 源):dnf info java-17-openjdk-devel