CentOS 8 安装 Apache Commons JCI 指南
在 CentOS 8 上从零安装 Apache Commons JCI 1.1,覆盖 EOL 后阿里云归档源修复、JDK 17 安装、Maven 不读 alternatives 的 JAVA_HOME 实测解法、Maven 依赖与 jar 直用两种方案及最小验证,命令回显均实测通过。
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 是库而非服务,环境中不存在需要常驻的守护进程;「安装完成」的判定标准是可编译并运行一个调用其
JavaCompilerAPI 的最小程序,而不是某个端口处于监听状态。
二、前置条件与 EOL 源修复
本节处理 CentOS 8 特有的前置障碍:系统 EOL 导致的软件源整体失效。跳过这一步,后续 dnf install 全部无法执行。
2.1 前置条件清单
- 具有 root 或 sudo 权限的 CentOS 8 系统。
- 可访问阿里云镜像站
mirrors.aliyun.com(CentOS 8 归档源的唯一可行渠道之一)。 - 可访问 Maven 中央仓库
repo1.maven.org(JCI 全部以 Maven 中央仓库为分发渠道)。 - 至少 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-core | 1.1 | commons-io 2.2、commons-logging 1.1.3 | 可用 |
| commons-jci-eclipse | 1.1 | ecj 4.2.2 | 可用(版本上限 1.7) |
| commons-jci-janino | 1.1 | janino 2.6.1 | 可用(不支持泛型) |
| commons-jci-javac | 1.0 | org.vafer:dependency 0.2 | 不可用(JDK 9+ 字节码格式) |
| commons-jci-fam | 1.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。
十、参考链接
- Apache Commons JCI 官方主页:https://commons.apache.org/proper/commons-jci/
- Maven 中央仓库 commons-jci-core 坐标:https://repo1.maven.org/maven2/org/apache/commons/commons-jci-core/
- Maven 中央仓库 commons-jci-eclipse 坐标:https://repo1.maven.org/maven2/org/apache/commons/commons-jci-eclipse/
- 阿里云 CentOS 归档镜像:https://mirrors.aliyun.com/centos-vault/
- OpenJDK 17(el8 包名查询):
dnf list java-17-openjdk-devel