CentOS 8 安装 Apache Commons JCI 指南

在 CentOS 8 上从零安装 Apache Commons JCI 1.1,覆盖 EOL 后阿里云归档源修复、JDK 17 安装、Maven 不读 alternatives 的 JAVA_HOME 实测解法、Maven 依赖与 jar 直用两种方案及最小验证,命令回显均实测通过。

Apache Commons JCIApache Commons JCI2026/10/15

CentOS 8 安装 Apache Commons JCI 指南

目标:在 CentOS 8 上从零搭建 Apache Commons JCI 1.1(Java 编译器接口库)的开发与运行环境:修复 EOL 后失效的软件源、安装 java-17-openjdk、用显式 JAVA_HOME 解决 el8 Maven 绑定旧 JDK 的问题,并跑通 eclipse 与 janino 编译器适配的内存编译闭环;本文全部命令与回显均已在 CentOS 8 实测通过。

Apache Commons JCI(Java Compiler Interface)把 Eclipse JDT(ecj)、Janino、javac、Groovy 等多种编译器统一成一套 JavaCompiler API,配合内存读写器与存储器实现「不落盘的内存编译」。它是纯 Java 库,没有常驻进程,安装的本质就是「修好软件源 → 装好 JDK/构建工具 → 引入依赖 → 写最小验证代码 → 编译运行」。CentOS 8 已于 2021-12-31 停止维护(EOL),官方源整体下线,本文将如实记录用阿里云归档源恢复安装能力的过程——这是 CentOS 8 上装任何软件都绕不开的第一步。

一、环境信息

本节先固定可复现的基线环境,后续所有命令均在此环境执行。

  • 操作系统:CentOS Linux release 8.4.2105(cat /etc/redhat-release 实测)
  • 架构:x86_64
  • 软件源:阿里云 CentOS 归档 mirrors.aliyun.com/centos-vault/8.5.2111(CentOS 8 EOL 后官方源已 403,见第二章)
  • JDK:java-17-openjdk-17.0.1.0.12-2.el8_5(dnf 包,/usr/lib/jvm/java-17)
  • 构建工具:Apache Maven 3.5.4 (Red Hat 3.5.4-5)(el8 AppStream 内置版本)
  • 组件版本:commons-jci-core 1.1(另有 eclipse 1.1、janino 1.1、fam 1.1 子模块)
  • Maven 坐标:org.apache.commons:commons-jci-core:1.1
  • 验证用工作目录:/opt/jci-demo(Maven 工程)、/opt/jci-lib 与 /opt/jci-app(jar 直用)

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

cat /etc/redhat-release
CentOS Linux release 8.4.2105 (Core)
uname -m
x86_64

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

二、前置条件与 EOL 源修复

本节处理 CentOS 8 特有的前置障碍:系统 EOL 导致的软件源整体失效。跳过这一步,后续 dnf install 全部无法执行。

2.1 前置条件清单

  1. 具有 root 或 sudo 权限的 CentOS 8 系统。
  2. 可访问阿里云镜像站 mirrors.aliyun.com(CentOS 8 归档源的唯一可行渠道之一)。
  3. 可访问 Maven 中央仓库 repo1.maven.org(JCI 全部以 Maven 中央仓库为分发渠道)。
  4. 至少 2 GB 可用磁盘空间(JDK + Maven + 依赖合计约 500 MB)。

2.2 EOL 现象确认

CentOS 8 官方源域名 mirror.centos.org 已整体下线,任何 dnf 操作都会失败。装好系统的机器不做处理时,dnf makecache 类操作会长时间卡在镜像解析并最终报 Errors during downloading metadata;官方归档站 vault.centos.org 对 CentOS 8 的部分路径也会返回 403(阿里云、清华等镜像站仍完整保留归档,实测 mirrors.aliyun.com/centos-vault/ 可用)。

2.3 写入阿里云归档源

把默认的 CentOS-Linux-*.repo 移除,写入指向 centos-vault/8.5.2111 的归档源。固定用 8.5.2111:这是 el8 仓库里同时含有 java-17-openjdk 与完整 BaseOS/AppStream 的版本线,低于 8.5 的归档没有 java-17 包。

rm -f /etc/yum.repos.d/CentOS-Linux-*.repo
cat > /etc/yum.repos.d/CentOS-Vault.repo <<'EOF'
[BaseOS-vault]
name=CentOS-8.5.2111 - BaseOS
baseurl=http://mirrors.aliyun.com/centos-vault/8.5.2111/BaseOS/$basearch/os/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
enabled=1

[AppStream-vault]
name=CentOS-8.5.2111 - AppStream
baseurl=http://mirrors.aliyun.com/centos-vault/8.5.2111/AppStream/$basearch/os/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
enabled=1
EOF
dnf makecache
CentOS-8.5.2111 - AppStream                     1.2 MB/s | 8.4 MB     00:07
Last metadata expiration check: 0:00:01 ago on Thu Oct 15 05:43:19 2026.
Metadata cache created.

验证点:Metadata cache created. 表示归档源可用;dnf install 之前能顺利走完元数据下载即为通过。

⚠️ 注意:不要用「只把 mirrorlist 注释、baseurl 指到归档根」的改法——默认 repo 文件里的 $contentdir/$releasever 变量会展开成 centos/8,拼出 centos-vault/8.5.2111/centos/8/AppStream/x86_64/os/ 这种不存在的路径并报 404(本文实测踩过)。直接整文件重写、把 8.5.2111 写死在 baseurl 里最可靠。

三、版本选型说明

JCI 的版本谱系与维护状态已在安装系列统一实测(版本事实全部来自 Maven Central metadata 与 jar 响应头实时查询):org.apache.commons 下的 core/eclipse/janino/groovy/fam 五个模块最新版均为 1.1(jar 发布于 2013-10-09),javac 模块止步 1.0(2010 年),此后约 13 年无更新。

在 CentOS 8 上版本选择的第二个维度是 JDK:el8 归档源里可用的 JDK 有 java-1.8.0、java-11(部分版本线)与 java-17(仅 8.5.2111 归档)。JCI 1.1 的核心链路(eclipse/janino 适配、内存编译、热更新监听)在 JDK 8 与 JDK 17 上实测均可运行,本文选 java-17-openjdk 17.0.1.0.12-2.el8_5 作为构建与运行环境,理由:el8 归档中的最新 LTS、与 Ubuntu 侧(17.0.20.1)同代可比对。

各模块速查表(与 Maven Central 实测一致):

模块最新版传递依赖(实测)JDK 17 可用性(实测)
commons-jci-core1.1commons-io 2.2、commons-logging 1.1.3可用
commons-jci-eclipse1.1ecj 4.2.2可用(版本上限 1.7)
commons-jci-janino1.1janino 2.6.1可用(不支持泛型)
commons-jci-javac1.0org.vafer:dependency 0.2不可用(JDK 9+ 字节码格式)
commons-jci-fam1.1无可用(热更新必需)

本文实际采用 1.1,依赖组合为 core + eclipse + janino + fam(不含 javac 模块,理由见 4.4 节)。

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

本节面向常规 Java 项目开发:dnf 安装 JDK 17 与 Maven,用显式 JAVA_HOME 修正 Maven 的 JDK 绑定,再引入 JCI 依赖跑通最小验证程序。

4.1 方案说明

方案 A 的链路是:dnf install java-17-openjdk-devel maven → 显式导出 JAVA_HOME=/usr/lib/jvm/java-17(el8 关键步骤,见 4.3 节实测)→ 在 pom.xml 中声明 JCI 坐标 → mvn 自动解析依赖 → 编译运行验证程序。

4.2 安装 JDK 17 与 Maven

dnf install -y java-17-openjdk-devel
# 输出节选(实测首尾)
  xorg-x11-fonts-Type1-7.5-19.el8.noarch

确认安装产物(/usr/lib/jvm 下的真实目录名,实时回显):

ls /usr/lib/jvm/ | grep 17
java-17
java-17-openjdk
java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64
jre-17
jre-17-openjdk
jre-17-openjdk-17.0.1.0.12-2.el8_5.x86_64
dnf install -y maven
# 输出节选(实测首尾)
  which-2.21-16.el8.x86_64

4.3 关键步骤:显式 JAVA_HOME(el8 的 Maven 不读 alternatives)

这是 CentOS 8 与 Ubuntu 最大的差异点,本文完整实测了三组证据。

证据一:装完 java-17 后,java 命令默认仍指向 1.8(alternatives auto 模式没有优先选 17):

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

证据二:即使把 alternatives 显式切到 java-17(java -version 已确认输出 17.0.1),Maven 依然绑定 1.8——el8 的 Maven 3.5.4 不读 alternatives:

alternatives --set java java-17-openjdk.x86_64
java -version 2>&1 | head -1
openjdk version "17.0.1" 2021-10-19 LTS
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

证据三:显式设置 JAVA_HOME 后 Maven 立即绑定 17:

export JAVA_HOME=/usr/lib/jvm/java-17
export PATH=$JAVA_HOME/bin:$PATH
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

验证点:Java version: 17.0.1 即修正成功。每开一个新 shell 都要重新导出,持久化做法是写入 /etc/profile.d/java17.sh:

cat > /etc/profile.d/java17.sh <<'EOF'
export JAVA_HOME=/usr/lib/jvm/java-17
export PATH=$JAVA_HOME/bin:$PATH
EOF
chmod +x /etc/profile.d/java17.sh

另注意回显末行 platform encoding: ANSI_X3.4-1968:el8 默认 locale 下平台编码是 ASCII,编译含中文源码的工程需在 pom 声明 project.build.sourceEncoding=UTF-8(本文 pom 已含),运行中文输出需加 -Dfile.encoding=UTF-8。

4.4 创建工程并引入依赖

建立 Maven 工程。刻意不引入 commons-jci-javac:它在 JDK 9 及以上不可用(反汇编实测其字节码转换层不认识 JDK 9+ 的 class 文件格式),且其传递依赖 org.vafer:dependency 0.2 会把 compile 作用域的 junit 3.8.2 与 asm 2.2.x 一并拖入。

mkdir -p /opt/jci-demo/src/main/java && cd /opt/jci-demo

pom.xml(完整内容,文件路径 /opt/jci-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>jci-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

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

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-jci-core</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-jci-eclipse</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-jci-janino</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-jci-fam</artifactId>
      <version>1.1</version>
    </dependency>
  </dependencies>
</project>

逐项说明:

  • project.build.sourceEncoding=UTF-8:el8 默认 ANSI_X3.4-1968 编码(见 4.3 节回显),中文源码不声明会编译报错。
  • commons-jci-core:1.1:门面核心;commons-jci-eclipse:1.1:Eclipse 编译器适配(自动带入 ecj 4.2.2);commons-jci-janino:1.1:Janino 适配;commons-jci-fam:1.1:文件变更监听模块(零传递依赖)。
  • maven.compiler.source/target=8:宿主工程按 Java 8 语法编译,JDK 17 可执行。

4.5 编译与运行验证

验证程序 Probe2.java(文件路径 /opt/jci-demo/src/main/java/Probe2.java,完整代码)对 eclipse 与 janino 两个适配器分别执行「内存源码 → 编译 → 取字节码 → 自定义 ClassLoader 加载 → 反射调用」闭环:

import org.apache.commons.jci.compilers.CompilationProblem;
import org.apache.commons.jci.compilers.CompilationResult;
import org.apache.commons.jci.compilers.JavaCompiler;
import org.apache.commons.jci.compilers.JavaCompilerFactory;
import org.apache.commons.jci.compilers.JavaCompilerSettings;
import org.apache.commons.jci.readers.MemoryResourceReader;
import org.apache.commons.jci.stores.MemoryResourceStore;

import java.lang.reflect.Method;

public class Probe2 {

    static final String SRC = "public class Hello { public String say() { return \"hello-\" + Math.max(1, 2); } }\n";

    public static void main(String[] args) throws Exception {
        run("eclipse", "1.6", "1.6");
        run("eclipse", "1.8", "1.8");
        run("janino", null, null);
    }

    static void run(String name, String source, String target) throws Exception {
        System.out.println("---- [" + name + " source=" + source + " target=" + target + "] ----");
        try {
            JavaCompiler compiler = JavaCompilerFactory.getInstance().createCompiler(name);
            JavaCompilerSettings settings = compiler.createDefaultSettings();
            if (source != null) {
                settings.setSourceVersion(source);
                settings.setTargetVersion(target);
            }
            MemoryResourceReader reader = new MemoryResourceReader();
            reader.add("Hello.java", SRC.getBytes("UTF-8"));
            MemoryResourceStore store = new MemoryResourceStore();
            CompilationResult result = compiler.compile(
                new String[]{"Hello.java"}, reader, store, Probe2.class.getClassLoader(), settings);
            System.out.println("errors=" + result.getErrors().length + " warnings=" + result.getWarnings().length);
            CompilationProblem[] errs = result.getErrors();
            int i = 0;
            do {
                if (i < errs.length) {
                    System.out.println("  err: " + errs[i].getMessage());
                }
                i++;
            } while (i < errs.length);
            byte[] bytes = store.read("Hello.class");
            System.out.println("bytecode bytes=" + (bytes == null ? -1 : bytes.length));
            if (bytes != null) {
                Class<?> cls = new ByteLoader(Probe2.class.getClassLoader()).define(bytes);
                Object hello = cls.getDeclaredConstructor().newInstance();
                Method say = cls.getMethod("say");
                System.out.println("invoke say() = " + say.invoke(hello));
            }
        } catch (Throwable t) {
            System.out.println("FAILED: " + t.getClass().getName() + ": " + String.valueOf(t.getMessage()));
        }
    }

    static class ByteLoader extends ClassLoader {
        ByteLoader(ClassLoader parent) { super(parent); }
        Class<?> define(byte[] bytes) {
            return defineClass("Hello", bytes, 0, bytes.length);
        }
    }
}

编译、归集依赖 jar 并运行:

cd /opt/jci-demo
export JAVA_HOME=/usr/lib/jvm/java-17 && export PATH=$JAVA_HOME/bin:$PATH
mvn -B -q clean compile && echo "EXIT=$?"
mvn -B -q dependency:copy-dependencies -DoutputDirectory=/opt/jci-demo/lib
java -Dfile.encoding=UTF-8 -cp "target/classes:lib/*" Probe2
EXIT=0
---- [eclipse source=1.6 target=1.6] ----
errors=0 warnings=0
bytecode bytes=536
invoke say() = hello-2
---- [eclipse source=1.8 target=1.8] ----
FAILED: java.lang.RuntimeException: unknown version 1.8
---- [janino source=null target=null] ----
errors=0 warnings=0
bytecode bytes=371
invoke say() = hello-2

结果解读:eclipse 适配器在 1.6 下完成内存编译(536 字节)并加载调用成功(hello-2);janino 零配置开箱即用(371 字节);eclipse 设 1.8 报 unknown version 1.8 是 ecj 4.2.2 的版本上限(1.4–1.7),属预期行为。与 Ubuntu 侧同版本实测结果一致,说明 JCI 1.1 在 CentOS 8 + JDK 17 上完整可用。

五、方案 B:中央仓库 jar 直用(离线友好)

本节面向无构建体系场景:从 Maven 中央仓库下载 eclipse 适配器最小 jar 集(core + eclipse + ecj + commons-io + commons-logging),校验后用 javac/java 完成 compile 与运行。

5.1 下载与完整性校验

mkdir -p /opt/jci-lib /opt/jci-app && cd /opt/jci-lib
B=https://repo1.maven.org/maven2
curl -sO $B/org/apache/commons/commons-jci-core/1.1/commons-jci-core-1.1.jar
curl -sO $B/org/apache/commons/commons-jci-eclipse/1.1/commons-jci-eclipse-1.1.jar
curl -sO $B/org/eclipse/jdt/core/compiler/ecj/4.2.2/ecj-4.2.2.jar
curl -sO $B/commons-io/commons-io/2.2/commons-io-2.2.jar
curl -sO $B/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar
ls -l | awk '{print $5, $9}'
173587 commons-io-2.2.jar
32404 commons-jci-core-1.1.jar
20459 commons-jci-eclipse-1.1.jar
62050 commons-logging-1.1.3.jar
1801636 ecj-4.2.2.jar

sha1 校验(期望值取自中央仓库同名 .sha1 文件,实测值来自本地计算):

cd /opt/jci-lib
B=https://repo1.maven.org/maven2
curl -s $B/org/apache/commons/commons-jci-core/1.1/commons-jci-core-1.1.jar.sha1 | xargs echo "core 期望:"
sha1sum commons-jci-core-1.1.jar | cut -d' ' -f1 | xargs echo "core 实测:"
curl -s $B/org/eclipse/jdt/core/compiler/ecj/4.2.2/ecj-4.2.2.jar.sha1 | xargs echo "ecj  期望:"
sha1sum ecj-4.2.2.jar | cut -d' ' -f1 | xargs echo "ecj  实测:"
core 期望: 765f587c613cc59459c1811d9e85271fc722af55
core 实测: 765f587c613cc59459c1811d9e85271fc722af55
ecj  期望: d9fd79499c664bccaf24dbe04be417de6c6af64a
ecj  实测: d9fd79499c664bccaf24dbe04be417de6c6af64a

5.2 编译与运行

验证程序 Demo.java(文件路径 /opt/jci-app/Demo.java)用 eclipse 适配器把源码字符串编译为字节码并加载执行:

import org.apache.commons.jci.compilers.CompilationResult;
import org.apache.commons.jci.compilers.JavaCompiler;
import org.apache.commons.jci.compilers.JavaCompilerFactory;
import org.apache.commons.jci.compilers.JavaCompilerSettings;
import org.apache.commons.jci.readers.MemoryResourceReader;
import org.apache.commons.jci.stores.MemoryResourceStore;

public class Demo {
    public static void main(String[] args) throws Exception {
        JavaCompiler compiler = new JavaCompilerFactory().createCompiler("eclipse");
        JavaCompilerSettings settings = compiler.createDefaultSettings();
        settings.setSourceVersion("1.6");
        settings.setTargetVersion("1.6");
        MemoryResourceReader reader = new MemoryResourceReader();
        String src = "public class Greet { public String hi() { return \"jci-ok-\" + Integer.MAX_VALUE; } }";
        reader.add("Greet.java", src.getBytes("UTF-8"));
        MemoryResourceStore store = new MemoryResourceStore();
        CompilationResult result = compiler.compile(
            new String[]{"Greet.java"}, reader, store, Demo.class.getClassLoader(), settings);
        System.out.println("errors=" + result.getErrors().length);
        byte[] bytes = store.read("Greet.class");
        System.out.println("bytecode bytes=" + bytes.length);
        Class<?> cls = new Sub(Demo.class.getClassLoader()).define(bytes);
        System.out.println(cls.getMethod("hi").invoke(cls.getDeclaredConstructor().newInstance()));
    }

    static class Sub extends ClassLoader {
        Sub(ClassLoader p) { super(p); }
        Class<?> define(byte[] b) { return defineClass("Greet", b, 0, b.length); }
    }
}

编译与运行(注意:jar 直用场景没有 Maven 帮忙定位 JDK,javac/java 来自 PATH 中 alternatives 指向的默认 JDK,必须先把 17 挂到 PATH 最前面):

cd /opt/jci-app
export JAVA_HOME=/usr/lib/jvm/java-17 && export PATH=$JAVA_HOME/bin:$PATH
javac -encoding UTF-8 -cp "/opt/jci-lib/*" -d classes Demo.java
java -Xms256m -Xmx256m -Dfile.encoding=UTF-8 -cp "classes:/opt/jci-lib/*" Demo
errors=0
bytecode bytes=350
jci-ok-2147483647

验证点:jci-ok-2147483647 即动态编译出的 Greet.hi() 返回 Integer.MAX_VALUE;本命令同时演示了 JVM 堆参数的写法与位置——-Xms256m -Xmx256m 加在 java 之后、主类之前,-Xms 调初始堆、-Xmx 调最大堆(建议 256–512 MB),需要持久生效时写入启动脚本或 systemd 的 Environment=JAVA_OPTS=-Xms256m -Xmx256m 并由 java $JAVA_OPTS -cp "classes:/opt/jci-lib/*" Demo 引用。

六、方案对比与选择建议

维度方案 A:Maven 依赖方案 B:中央仓库 jar 直用
运行期依赖Maven 自动解析手工下载 5 个 jar 拼 classpath
是否联网首次构建需联网仅下载时需要联网
JDK 绑定需显式 JAVA_HOME(4.3 节实测)javac/java 取自 PATH,需把 17 挂最前
依赖版本管控pom 统一声明靠文件名区分,手工对齐
适用场景常规工程开发离线脚本、临时验证、无构建体系

选型建议:有构建体系选方案 A(注意 4.3 节的 JAVA_HOME 是 CentOS 特有必做项);无构建工具的老旧服务器或脚本化分发选方案 B。两者可共存——方案 A 开发、方案 B 分发 jar。

七、基础配置说明

JCI 是库而非服务,没有独立配置文件,全部行为通过代码参数与环境变量控制。CentOS 8 上真正需要配置的是两个环境变量:

export JAVA_HOME=/usr/lib/jvm/java-17
export PATH=$JAVA_HOME/bin:$PATH
echo "JAVA_HOME=$JAVA_HOME"
JAVA_HOME=/usr/lib/jvm/java-17

逐项说明:

  • JAVA_HOME:CentOS 8 上是必设项。实测 Maven 3.5.4 不读 alternatives(4.3 节证据二:alternatives 已切 17 但 mvn 仍绑 1.8.0_312),不设则构建用错 JDK。持久化写入 /etc/profile.d/java17.sh(4.3 节已给出全文)。
  • 代码级「参数」:编译器版本由 JavaCompilerSettings 控制,ecj 4.2.2 合法取值 1.4–1.7(设 1.8 实测抛 unknown version 1.8);janino 2.6.1 不感知版本设置。堆参数见 5.2 节。API 语义详见本专栏《Apache Commons JCI 1.1 编译器适配与 API 详解》。

八、常见问题(FAQ)

Q1:dnf makecache 报 403 或 Errors during downloading metadata? CentOS 8 已 EOL,官方源 mirror.centos.org 下线,vault.centos.org 部分路径也 403。解法:按 2.3 节整文件重写 repo 指向 mirrors.aliyun.com/centos-vault/8.5.2111(实测可用)。注意必须整文件重写而不是仅注释 mirrorlist——后者会因 $contentdir/$releasever 变量展开出 centos-vault/8.5.2111/centos/8/AppStream/x86_64/os/ 的 404 路径(本文实测踩过)。

Q2:装了 java-17,mvn -version 还是显示 1.8? 两个原因叠加:一是 el8 的 Maven 3.5.4 不读 alternatives(4.3 节证据二实测:alternatives 切到 17.0.1 后 mvn 仍绑 1.8.0_312);二是 alternatives auto 模式本身默认也没把 java 切到 17(证据一)。解法:export JAVA_HOME=/usr/lib/jvm/java-17 并持久化到 /etc/profile.d/java17.sh,每个新 shell 生效需 source /etc/profile.d/java17.sh 或重新登录。

Q3:编译含中文注释的源码报 unmappable character? el8 默认 locale 的平台编码是 ANSI_X3.4-1968(4.3 节 mvn -version 回显末行)。Maven 工程在 pom 加 project.build.sourceEncoding=UTF-8;javac 命令行加 -encoding UTF-8(本文 5.2 节命令已带);运行中文输出加 -Dfile.encoding=UTF-8。

Q4:javac 适配器在 JDK 17 下能用吗? 不能。实测调用 createCompiler("javac") 后返回错误 Error while executing the compiler: java.lang.reflect.InvocationTargetException。根因(反汇编与逐步复现实测):jci-javac 1.0 的 JavacClassLoader 用 2006 年的 asm 2.2 对 javac 字节码做内存化改写,不认识 JDK 9+ class 文件格式,JDK 17 下报 ClassFormatError: Nest member class_info_index 129 has bad constant type in class file com/sun/tools/javac/main/Main。CentOS 8 若必须用 javac 风格编译,用 java-1.8.0 运行 JCI(需 -noverify 且源文件需在磁盘存在同名占位文件,详见本专栏 API 详解篇),或改用 eclipse 适配器(本文主线)。

Q5:JDK 8 与 JDK 17 上 JCI 行为有差异吗? 核心链路一致(eclipse/janino 适配、内存编译、热更新监听双 JDK 实测结果相同)。差异在适配器边界:groovy 适配器 JDK 8 开箱即用,JDK 17 需 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED;javac 适配器 JDK 8 加 -noverify 后有限可用、JDK 17 完全不可用。完整矩阵见本专栏 API 详解篇。

九、卸载与清理

移除本文创建的工程、jar 与工作目录:

rm -rf /opt/jci-demo /opt/jci-lib /opt/jci-app
ls /opt/jci-demo 2>&1
ls: cannot access '/opt/jci-demo': No such file or directory

移除 Maven 本地仓库中的 JCI 相关构件(影响所有工程,慎用):

rm -rf $HOME/.m2/repository/org/apache/commons/commons-jci-core
rm -rf $HOME/.m2/repository/org/apache/commons/commons-jci-eclipse
rm -rf $HOME/.m2/repository/org/apache/commons/commons-jci-janino
rm -rf $HOME/.m2/repository/org/apache/commons/commons-jci-fam
rm -rf $HOME/.m2/repository/org/eclipse/jdt/core/compiler/ecj/4.2.2

卸载 JDK 与 Maven 前先预演(--assumeno 只显示事务不执行,实测回显如下),确认将删除的包无误后去掉该参数真正执行:

dnf remove --assumeno java-17-openjdk-devel maven
Removing:
Removing unused dependencies:
Operation aborted.

确认无误后真正卸载(把 --assumeno 换成 -y):

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

如需彻底还原软件源(不推荐,会导致后续无法安装软件),删除归档 repo:rm -f /etc/yum.repos.d/CentOS-Vault.repo /etc/profile.d/java17.sh。

十、参考链接

  1. Apache Commons JCI 官方主页:https://commons.apache.org/proper/commons-jci/
  2. Maven 中央仓库 commons-jci-core 坐标:https://repo1.maven.org/maven2/org/apache/commons/commons-jci-core/
  3. Maven 中央仓库 commons-jci-eclipse 坐标:https://repo1.maven.org/maven2/org/apache/commons/commons-jci-eclipse/
  4. 阿里云 CentOS 归档镜像:https://mirrors.aliyun.com/centos-vault/
  5. OpenJDK 17(el8 包名查询):dnf list java-17-openjdk-devel