Ubuntu 22.04 安装 Apache Commons JXPath 指南
在 Ubuntu 22.04 上从零引入 Apache Commons JXPath 1.4.0:旧 groupId 选型要点、Maven 在线依赖与官方二进制包两种安装方案、依赖树核实与 Bean 对象图导航运行验证,全部命令附真实回显。
Ubuntu 22.04 安装 Apache Commons JXPath 指南
目标:在 Ubuntu 22.04 上从零搭建 Apache Commons JXPath 1.4.0 的使用环境,完成 JDK 17 + Maven 环境确认、在线 Maven 依赖与官方二进制包两种获取方式,并用真实的 Bean 对象图导航程序验证安装结果。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)实测通过。
一、环境信息
本节先固定本次实测所用软硬件基线,便于读者逐项对照复现。
- 操作系统:Ubuntu 22.04(x86_64)
- JDK:OpenJDK 17.0.20.1(
java -version实测,回显见 4.2) - 构建工具:Apache Maven 3.6.3(
mvn -v实测,回显见 4.2) - 目标库:Apache Commons JXPath 1.4.0(Maven 坐标
commons-jxpath:commons-jxpath:1.4.0,注意是旧 groupId,见第三章) - 依赖来源:
https://repo.maven.apache.org/maven2/(Maven 中央仓库)、https://downloads.apache.org/commons/jxpath/(官方发行目录),网络直连可达 - Maven 示例工程目录:
/opt/jxpath-demo - 官方二进制包解压目录:
/opt/jxpath-dist - 二进制包演示目录:
/opt/jxpath-dist-demo
Commons JXPath 是 Apache 基金会的对象图导航库(对象图导航指用 XPath 表达式访问 JavaBean 属性、集合与 XML 文档,选型对比见第三章技术拓展)。它以 jar 形式嵌入应用,无独立守护进程、无端口,安装本质上是「让应用能拿到这一个 jar」。
二、前置条件
在开始安装前,请确认以下条件满足,否则后续步骤会因权限或网络问题中断。
- root 权限或 sudo:需要在
/opt下创建目录并写入文件。 - 网络可达:需要访问
repo.maven.apache.org(Maven 中央仓库)与downloads.apache.org(官方二进制包)。先做一次连通性预检,两个地址各探测一次(该命令同时验证了旧 groupId 坐标的 POM 确实存在于中央仓库):
curl -s -o /dev/null -w "%{http_code}\n" --connect-timeout 10 https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.pom
curl -s -o /dev/null -w "%{http_code}\n" --connect-timeout 10 https://downloads.apache.org/commons/jxpath/binaries/commons-jxpath-1.4.0-bin.tar.gz
输出:
200
200
两个 200 分别确认:中央仓库上 commons-jxpath:commons-jxpath:1.4.0 的 POM 可达(方案 A 可用)、官方二进制包可达(方案 B 可用)。
- 磁盘空间:官方二进制包实测 3,374,636 字节(约 3.4 MB),加上 Maven 本地仓库缓存建议预留 ≥ 200 MB。
- JDK 与 Maven 已就绪:本文统一使用 JDK 17(回显验证见 4.2)。若系统未装,先执行
apt-get install -y openjdk-17-jdk maven安装。
⚠️ 注意:若内网无法直连上述域名,需按企业代理配置放行,否则 Maven 依赖与官方包均无法获取。本文实测环境为直连。
三、版本选型说明
安装前先确认应选用的版本与坐标,JXPath 有两个与 Commons 家族其他组件不同的关键点:版本线断代、groupId 沿用旧名。本节逐项说明并给出选型结论。
3.1 版本线:1.3(2008)与 1.4.0 两代
JXPath 的版本历史是「断代复活」型:1.3 发布于 2008 年,此后项目沉寂十余年,直到 2025 年 4 月才恢复发行 1.4.0。当前可选版本只有这两代:
| 版本 | 状态 | 说明 | 建议 |
|---|---|---|---|
| 1.4.0 | 当前唯一活跃正式版(2025-04 恢复发行) | 本文实测版本,依赖已刷新(commons-parent 81、commons-logging 1.3.5 管理) | 生产环境首选 |
| 1.3 | 历史版本(2008 年) | 断代 17 年,构建生态与依赖陈旧 | 仅存量系统维护,新项目勿用 |
解读:两版之间没有中间版本,从 1.3 升级到 1.4.0 属于跨代升级;新项目一律直接用 1.4.0,存量 1.3 项目升级前建议用本文第四章的最小程序先做回归验证。
3.2 groupId 陷阱:沿用旧坐标 commons-jxpath
JXPath 是 Commons 家族中少数不使用 org.apache.commons groupId 的组件,1.4.0 仍由历史坐标发布。实测坐标结论:
| Maven 坐标 | 可用性 | 说明 |
|---|---|---|
commons-jxpath:commons-jxpath:1.4.0 | ✅ 可用(本文实测) | 历史沿用的旧 groupId,1.4.0 仍由该坐标发布 |
org.apache.commons:commons-jxpath:1.4.0 | ❌ 不存在(404) | 中央仓库该路径下没有构件,照抄 Commons 惯例会解析失败 |
解读:这是引入 JXPath 时最容易踩的坑——多数 Commons 组件(commons-io、commons-lang3 等)都在 org.apache.commons 下,惯性写法会直接 404。本文第二章连通性预检返回的 200 证实了旧坐标 POM 真实存在;4.4 节编译日志中的 Downloading 行也会再次印证。
3.3 依赖构成:无强制传递依赖
对 1.4.0 的 POM 与依赖树做了实测核实(完整命令与回显见 4.5 节),结论先行:
- 依赖树只有它自己:
mvn dependency:tree仅解析出commons-jxpath:commons-jxpath:jar:1.4.0:compile一行,无任何强制传递依赖。 - commons-logging 1.3.5 只出现在 POM 的
<dependencyManagement>中(仅锁版本,不是实际依赖);实测运行期 classpath 不带它也完全不报错。 - servlet-api / jsp-api 为
provided+optional(仅 JSP/Servlet 场景需要,由 Servlet 运行环境提供)。 - jdom 1.1.3、commons-beanutils 1.10.1 为
optional(JDOM 文档模型等扩展特性按需引入)。 - 没有 xerces 依赖:XML 解析使用 JDK 内置的
com.sun.org.apache.xerces实现(4.6 节运行输出中的类名可证)。
💡 技术拓展:「对象图导航/XPath 求值」在 Java 生态不止 JXPath 一种实现。常见选项对比——
库 导航模型 特点 适用场景 Commons JXPath XPath 1.0 同时作用于 JavaBean 对象图与 XML DOM 一套语法通吃 Bean 属性/集合与 XML;支持 Pointer、路径创建、变量与扩展函数 需要用同一表达式语法访问 JavaBean 和 XML Jaxen 独立 XPath 引擎,供 dom4j/JDOM/XOM 等树模型适配 XPath 引擎本体,不含 Bean 导航;常作为 dom4j 的底层 已用 dom4j/JDOM 处理 XML,只需 XPath javax.xml.xpath(JDK 内置) JDK 自带 XPath 1.0,面向 DOM/Source 零依赖,但只支持 XML,不支持 JavaBean 导航 仅 XML 求值、不想引第三方库 Commons BeanUtils / PropertyUtils 属性路径字符串( a.b[0].c)按 getter/setter 反射轻量嵌套属性读写,但不支持谓词过滤、函数、XPath 轴 简单嵌套属性取值/赋值、对象属性拷贝 Spring EL(SpEL) 表达式语言,支持属性导航、集合选择投影 功能强但绑定 Spring 体系,表达式非 XPath 方言 Spring 技术栈内的动态取值与条件求值 本文场景需要「同一套 XPath 语法既访问 JavaBean 对象图又访问 XML DOM」,选用 Commons JXPath 1.4.0:它无传递依赖、单 jar 即可运行,且 1.4.0 已于 2025 年恢复维护。
四、在线安装方案(A:Maven 依赖引入)
官方推荐方式:通过 Maven 引入依赖,由构建工具自动解析。JXPath 1.4.0 无传递依赖,解析结果最简单。适合可联网的开发/构建环境。
4.1 方案说明与适用场景
本方案先确认系统已具备 JDK 17 与 Maven,再创建一个最小 Maven 工程引入 commons-jxpath:commons-jxpath:1.4.0,通过首次编译核实依赖下载,用依赖树命令核实「无传递依赖」,最后运行一个覆盖核心用法的 Bean 导航程序完成验证。适合持续集成、可访问中央仓库的场景。
4.2 确认 JDK 17 与 Maven
先确认 JDK 版本、Maven 版本及其使用的 JDK,并创建本文统一的示例工程目录:
java -version 2>&1; mvn -v | head -3; mkdir -p /opt/jxpath-demo && echo DIR_OK
输出:
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
DIR_OK
解读:JDK 为 17.0.20.1;Java version: 17.0.20.1 说明系统 Maven 3.6.3 已自动使用 JDK 17(Ubuntu 下系统 Maven 跟随 java 的解析结果,无需设置 JAVA_HOME);DIR_OK 确认 /opt/jxpath-demo 已创建。
4.3 创建 Maven 工程并引入依赖
/opt/jxpath-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>jxpath-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-jxpath</groupId>
<artifactId>commons-jxpath</artifactId>
<version>1.4.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>
</configuration>
</plugin>
</plugins>
</build>
</project>
逐项说明:groupId 用旧坐标 commons-jxpath(第三章的选型要点,写成 org.apache.commons 会 404);project.build.sourceEncoding 设为 UTF-8(源码含中文注释时必须,否则 javac 报 unmappable character);maven-compiler-plugin 用 <release>17</release> 按 JDK 17 编译(release 参数比 source/target 更严格,避免误用高版本 API);依赖仅声明 JXPath 一个。
4.4 首次编译(含依赖下载核实)
编译工程(工程内已预先放入 demo/BeanNav.java 与 demo/XmlNav.java 两个示例源码:前者见 4.6 节,后者用于《Apache Commons JXPath 1.4.0 常用 API 与 XPath 语法详解》的 XML 示例),首次编译会从中央仓库下载 JXPath 构件:
cd /opt/jxpath-demo && mvn -B compile 2>&1 | tail -30
输出节选(实测 tail -30):
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< demo:jxpath-demo >--------------------------
[INFO] Building jxpath-demo 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.pom
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.pom (14 kB at 9.7 kB/s)
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-parent/81/commons-parent-81.pom
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-parent/81/commons-parent-81.pom (78 kB at 148 kB/s)
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/apache/33/apache-33.pom
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/apache/33/apache-33.pom (24 kB at 16 kB/s)
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.11.4/junit-bom-5.11.4.pom
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.11.4/junit-bom-5.11.4.pom (5.6 kB at 11 kB/s)
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.jar
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.jar (329 kB at 176 kB/s)
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ jxpath-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /opt/jxpath-demo/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ jxpath-demo ---
[INFO] Changes detected - recompiling the module! :dependency
[INFO] Compiling 2 source files with javac [debug release 17] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.138 s
[INFO] Finished at: 2026-10-14T10:23:36Z
[INFO] ------------------------------------------------------------------------
解读下载记录:真正下载的依赖 jar 只有 commons-jxpath-1.4.0.jar(329 kB)一个;其余 commons-parent-81.pom、apache-33.pom、junit-bom-5.11.4.pom 都是 JXPath 父 POM 链上的元数据文件(父 POM 与 BOM 仅用于依赖版本管理,不构成实际依赖,4.5 节的依赖树会证实这一点)。BUILD SUCCESS 同时确认了旧 groupId 坐标解析成功、release 17 编译通过。
4.5 依赖树核实:只有它自己
用依赖树与依赖列表两个命令交叉核实(后续升级、排障都会用到这两个命令):
cd /opt/jxpath-demo && mvn -B dependency:tree 2>&1 | sed -n "/maven-dependency-plugin/,\$p"
输出:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ jxpath-demo ---
[INFO] demo:jxpath-demo:jar:1.0.0
[INFO] \- commons-jxpath:commons-jxpath:jar:1.4.0:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.731 s
[INFO] Finished at: 2026-10-14T10:23:45Z
[INFO] ------------------------------------------------------------------------
cd /opt/jxpath-demo && mvn -B dependency:list 2>&1 | sed -n "/The following files/,/BUILD SUCCESS/p"; echo ===VERBOSE===; mvn -B dependency:tree -Dverbose 2>&1 | grep "compile\|test\|runtime\|provided\|omitted\|optional"
输出:
[INFO] The following files have been resolved:
[INFO] commons-jxpath:commons-jxpath:jar:1.4.0:compile
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
===VERBOSE===
[INFO] \- commons-jxpath:commons-jxpath:jar:1.4.0:compile
两个命令一致确认:依赖树只有 JXPath 自身一行,verbose 模式下也没有任何 omitted/optional 分支展开成实际依赖。
再看本地仓库中 JXPath 的 POM 片段,解释「为什么没有传递依赖」(逐行真实输出):
grep -A4 "<dependency>" ~/.m2/repository/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.pom | head -40
输出:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.3.5</version>
<scope>runtime</scope>
--
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
--
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
--
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
<optional>true</optional>
--
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.10.1</version>
<optional>true</optional>
--
<dependency>
<groupId>com.mockrunner</groupId>
<artifactId>mockrunner-jdk1.3-j2ee1.3</artifactId>
<version>0.4</version>
<scope>test</scope>
sed -n "126,180p" ~/.m2/repository/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.pom
输出:
<xmlOutput>true</xmlOutput>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.3.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.10.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mockrunner</groupId>
<artifactId>mockrunner-jdk1.3-j2ee1.3</artifactId>
<version>0.4</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>cglib-nodep</groupId>
逐项解读:第一段 grep 输出里最上面的 commons-logging 命中的其实是下面 <dependencyManagement> 块里的声明(sed 片段可看清结构)——dependencyManagement 只锁版本,不是实际依赖;servlet-api/jsp-api 为 provided+optional;junit-jupiter-engine、mockrunner 为 test;jdom、commons-beanutils 为 optional。这解释了依赖树为何只剩 JXPath 自身。
4.6 运行验证(Bean 对象图导航)
先由 Maven 生成运行期 classpath 文件,再编译运行验证程序 demo.BeanNav(源码附后):
cd /opt/jxpath-demo && mvn -B dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt -q 2>&1 | tail -3; echo CP=$(cat /tmp/cp.txt); java -cp target/classes:$(cat /tmp/cp.txt) demo.BeanNav 2>&1
输出(-q 静默模式下 build-classpath 无多余输出,CP= 即运行期 classpath):
CP=/root/.m2/repository/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.jar
== 1. getValue basic & predicate ==
getValue("name") = Acme Corp
getValue("employees[1].name") threw org.apache.commons.jxpath.JXPathInvalidSyntaxException
message: Invalid XPath: 'employees[1].name'. Syntax error after: 'employees[1]'
getValue("employees[1]/name") = Alice
getValue("employees[age > 30]/name") = Bob
== 2. iterate() over /employees ==
iterate -> Employee{name='Alice', age=28, address=Address{city='Beijing'}}
iterate -> Employee{name='Bob', age=35, address=Address{city='Shanghai'}}
iterate -> Employee{name='Carol', age=41, address=null}
== 3. iteratePointers() employees/address/city ==
path=/employees[1]/address/city value=Beijing
path=/employees[2]/address/city value=Shanghai
== 4. setValue (XPath index is 1-based: employees[2] = Bob) ==
after setValue(employees[2]/age, 36) -> Employee{name='Bob', age=36, address=Address{city='Shanghai'}}
== 5. createPathAndSetValue (auto create null path) ==
without AbstractFactory threw org.apache.commons.jxpath.JXPathException
root cause: Factory is not set on the JXPathContext - cannot create path: /employees[3]/address
with AbstractFactory, Carol now -> Employee{name='Carol', age=41, address=Address{city='Shenzhen'}}
== 6. declareVariable & $var ==
senior = Bob
senior = Carol
== 7. custom functions via ClassFunctions ==
util:reverse(name) = proC emcA
util:charCount(name) = 9
== 8. selectNodes on bean context ==
selectNodes(employees[age > 30]/name).size() = 2, names = [Bob, Carol]
验证点:CP= 行证明运行期 classpath 只有 JXPath 一个 jar(无 commons-logging 也无任何报错);8 组用例覆盖基本取值、谓词过滤、集合遍历、Pointer、修改、按路径创建、变量、扩展函数与多值查询,全部得到预期结果。第 1 组里 employees[1].name 抛 JXPathInvalidSyntaxException 是预期行为(谓词后必须用 / 续写,见第八章 Q1)。逐条语法解读见《Apache Commons JXPath 1.4.0 常用 API 与 XPath 语法详解》。
验证程序 /opt/jxpath-demo/src/main/java/demo/BeanNav.java 完整内容:
package demo;
import org.apache.commons.jxpath.AbstractFactory;
import org.apache.commons.jxpath.ClassFunctions;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.Pointer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* JXPath 1.4.0 Bean navigation demo.
*/
public class BeanNav {
public static class Address {
private String city;
public Address() {
}
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{city='" + city + "'}";
}
}
public static class Employee {
private String name;
private int age;
private Address address;
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age + ", address=" + address + "}";
}
}
public static class Company {
private String name;
private List<Employee> employees = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
public static class UtilFunctions {
public static String reverse(String s) {
return new StringBuilder(s).reverse().toString();
}
public static int charCount(String s) {
return s.length();
}
}
public static void main(String[] args) {
Company company = new Company();
company.setName("Acme Corp");
company.getEmployees().add(new Employee("Alice", 28, new Address("Beijing")));
company.getEmployees().add(new Employee("Bob", 35, new Address("Shanghai")));
company.getEmployees().add(new Employee("Carol", 41, null));
JXPathContext ctx = JXPathContext.newContext(company);
System.out.println("== 1. getValue basic & predicate ==");
System.out.println("getValue(\"name\") = " + ctx.getValue("name"));
try {
System.out.println("getValue(\"employees[1].name\") = " + ctx.getValue("employees[1].name"));
} catch (Exception e) {
System.out.println("getValue(\"employees[1].name\") threw " + e.getClass().getName());
System.out.println(" message: " + e.getMessage());
}
System.out.println("getValue(\"employees[1]/name\") = " + ctx.getValue("employees[1]/name"));
System.out.println("getValue(\"employees[age > 30]/name\") = " + ctx.getValue("employees[age > 30]/name"));
System.out.println();
System.out.println("== 2. iterate() over /employees ==");
Iterator<?> it = ctx.iterate("/employees");
while (it.hasNext()) {
System.out.println(" iterate -> " + it.next());
}
System.out.println();
System.out.println("== 3. iteratePointers() employees/address/city ==");
Iterator<?> ptrIt = ctx.iteratePointers("employees/address/city");
while (ptrIt.hasNext()) {
Pointer p = (Pointer) ptrIt.next();
System.out.println(" path=" + p.asPath() + " value=" + p.getValue());
}
System.out.println();
System.out.println("== 4. setValue (XPath index is 1-based: employees[2] = Bob) ==");
ctx.setValue("employees[2]/age", 36);
System.out.println(" after setValue(employees[2]/age, 36) -> " + company.getEmployees().get(1));
System.out.println();
System.out.println("== 5. createPathAndSetValue (auto create null path) ==");
try {
ctx.createPathAndSetValue("employees[name = 'Carol']/address/city", "Shenzhen");
System.out.println(" Carol now -> " + company.getEmployees().get(2));
} catch (Exception e) {
System.out.println(" without AbstractFactory threw " + e.getClass().getName());
Throwable c = e;
while (c.getCause() != null) {
c = c.getCause();
}
System.out.println(" root cause: " + c.getMessage());
ctx.setFactory(new AbstractFactory() {
@Override
public boolean createObject(JXPathContext context, Pointer pointer, Object parent,
String name, int index) {
if (parent instanceof Employee && "address".equals(name)) {
((Employee) parent).setAddress(new Address());
return true;
}
return false;
}
});
ctx.createPathAndSetValue("employees[name = 'Carol']/address/city", "Shenzhen");
System.out.println(" with AbstractFactory, Carol now -> " + company.getEmployees().get(2));
}
System.out.println();
System.out.println("== 6. declareVariable & $var ==");
ctx.getVariables().declareVariable("minAge", Integer.valueOf(30));
Iterator<?> seniors = ctx.iterate("employees[age > $minAge]/name");
while (seniors.hasNext()) {
System.out.println(" senior = " + seniors.next());
}
System.out.println();
System.out.println("== 7. custom functions via ClassFunctions ==");
ctx.setFunctions(new ClassFunctions(UtilFunctions.class, "util"));
System.out.println(" util:reverse(name) = " + ctx.getValue("util:reverse(name)"));
System.out.println(" util:charCount(name) = " + ctx.getValue("util:charCount(name)"));
System.out.println();
System.out.println("== 8. selectNodes on bean context ==");
List<?> names = ctx.selectNodes("employees[age > 30]/name");
System.out.println(" selectNodes(employees[age > 30]/name).size() = " + names.size() + ", names = " + names);
}
}
五、离线安装方案(B:官方二进制包)
适合内网隔离、无法访问 Maven 中央仓库的环境。JXPath 1.4.0 无传递依赖,官方二进制包解压后仅靠主 jar 一个文件即可用 javac/java 编译运行,无需补任何第三方 jar。
5.1 方案说明与适用场景
官方 commons-jxpath-1.4.0-bin.tar.gz 内含 JXPath 自身的 5 个 jar(主 jar、tests、test-sources、sources、javadoc)与 LICENSE/NOTICE/apidocs 文档,没有 lib/ 或 ext/ 目录、不附带 commons-logging。本方案流程:下载 → 解压 → 用主 jar 编译一个最小程序 → 运行验证。
5.2 下载与解压
用 curl 下载官方二进制包并解压到 /opt/jxpath-dist(--strip-components=1 去掉包内一层目录):
cd /opt && curl -sO https://downloads.apache.org/commons/jxpath/binaries/commons-jxpath-1.4.0-bin.tar.gz && ls -l commons-jxpath-1.4.0-bin.tar.gz && mkdir -p /opt/jxpath-dist && tar xzf commons-jxpath-1.4.0-bin.tar.gz -C /opt/jxpath-dist --strip-components=1 && find /opt/jxpath-dist -name "*.jar" | sort
输出:
-rw-r--r-- 1 root root 3374636 Oct 14 10:33 commons-jxpath-1.4.0-bin.tar.gz
/opt/jxpath-dist/commons-jxpath-1.4.0-javadoc.jar
/opt/jxpath-dist/commons-jxpath-1.4.0-sources.jar
/opt/jxpath-dist/commons-jxpath-1.4.0-test-sources.jar
/opt/jxpath-dist/commons-jxpath-1.4.0-tests.jar
/opt/jxpath-dist/commons-jxpath-1.4.0.jar
解读:包大小实测 3374636 字节(约 3.4 MB);5 个 jar 中日常只需 commons-jxpath-1.4.0.jar 主 jar,其余为测试、源码与 javadoc 附属包。
5.3 解压目录结构
确认发行包的完整结构:
ls /opt/jxpath-dist
输出:
LICENSE.txt
NOTICE.txt
apidocs
commons-jxpath-1.4.0-javadoc.jar
commons-jxpath-1.4.0-sources.jar
commons-jxpath-1.4.0-test-sources.jar
commons-jxpath-1.4.0-tests.jar
commons-jxpath-1.4.0.jar
解读:目录里只有许可证、HTML 版 javadoc(apidocs/)与 5 个 jar——没有 lib/、ext/ 目录,不带 commons-logging。这不是缺陷:1.4.0 无强制传递依赖(见 4.5 节),主 jar 单独就能跑,5.4 节立即验证。
5.4 最小程序编译运行验证
创建演示目录与最小程序 /opt/jxpath-dist-demo/Main.java(无包名,javac/java 直跑):
import org.apache.commons.jxpath.JXPathContext;
import java.util.ArrayList;
import java.util.List;
/**
* Minimal demo for the official binary distribution (commons-jxpath-1.4.0-bin.tar.gz).
* Compiled and run with plain javac/java + -cp.
*/
public class Main {
public static class Address {
private final String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
}
public static class Employee {
private final String name;
private final int age;
private final Address address;
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Address getAddress() {
return address;
}
}
public static class Company {
private final String name;
private final List<Employee> employees = new ArrayList<>();
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<Employee> getEmployees() {
return employees;
}
}
public static void main(String[] args) {
Company company = new Company("Acme Corp");
company.getEmployees().add(new Employee("Alice", 28, new Address("Beijing")));
company.getEmployees().add(new Employee("Bob", 35, new Address("Shanghai")));
JXPathContext ctx = JXPathContext.newContext(company);
System.out.println("name = " + ctx.getValue("name"));
System.out.println("employees[1]/name = " + ctx.getValue("employees[1]/name"));
System.out.println("employees[age > 30]/name = " + ctx.getValue("employees[age > 30]/name"));
System.out.println("employees/address/city = " + ctx.getValue("employees/address/city"));
}
}
编译并运行(classpath 只有 JXPath 主 jar 一个文件):
cd /opt/jxpath-dist-demo && javac -cp /opt/jxpath-dist/commons-jxpath-1.4.0.jar Main.java && echo JAVAC_OK && java -cp .:/opt/jxpath-dist/commons-jxpath-1.4.0.jar Main 2>&1
输出:
JAVAC_OK
name = Acme Corp
employees[1]/name = Alice
employees[age > 30]/name = Bob
employees/address/city = Beijing
验证点:JAVAC_OK 确认仅凭主 jar 即可编译;运行输出 4 行全部正确——employees[1]/name 取到第 1 个员工 Alice、谓词 age > 30 取到 Bob、多级路径 employees/address/city 沿 getEmployees() → getAddress() → getCity() 链取到北京。全程无 NoClassDefFoundError,证明不带 commons-logging 完全可行。
⚠️ 注意(真实踩坑沉淀):本程序第一版把路径写成了点式
employees[1].name,运行同样抛JXPathInvalidSyntaxException: Invalid XPath: 'employees[1].name'. Syntax error after: 'employees[1]',改为employees[1]/name后才输出上述正确结果。谓词或下标之后必须用/续写属性,这是 JXPath 语法最容易错的一处(详见第八章 Q1)。
六、多方案对照与选择建议
两种方案各有取舍,本节汇总关键差异并给出选型建议。
| 维度 | 方案 A:Maven 依赖 | 方案 B:官方二进制包 |
|---|---|---|
| 是否需联网 | 构建时需访问中央仓库 | 仅下载阶段需联网,之后完全离线 |
| 获取坐标 | commons-jxpath:commons-jxpath:1.4.0(旧 groupId) | downloads.apache.org 的 binaries/ 子目录 |
| 依赖完整性 | 自动解析(实测无传递依赖,同样只有单 jar) | 主 jar 即全部,无需补 jar |
| 附带产物 | 仅主 jar(sources/javadoc 需单独在 IDE 拉取) | 附带 sources/javadoc/tests jar 与 apidocs 文档 |
| 依赖管理 | 版本集中在 pom,升级改一行 | jar 文件手工管理,需自行跟踪版本 |
| 验证结果 | 与 B 一致 | 与 A 一致 |
选择建议:可联网优先方案 A(版本管理清晰,是工程项目的默认形态);内网隔离或只想快速试用选方案 B(一次下载 3.4 MB,解压即用,还自带源码与文档)。两方案的运行结果完全一致,可放心互换;方案 B 的主 jar 也可以直接拷进本地仓库或私服,作为方案 A 的离线补充。
七、配置说明
JXPath 是纯 Java 进程内库,无独立配置文件,运行期需要关注的「配置」集中在 JDK/Maven 环境与依赖坐标,本节逐项说明。
- JDK 版本:本文统一用 JDK 17.0.20.1,
release 17编译运行均无告警。JXPath 作为查询库无独立进程,堆需求随对象图规模而定,本文示例使用 JVM 默认堆即足够。 - 源码编码:
project.build.sourceEncoding设为 UTF-8;源码含中文注释时若缺该配置,javac会报unmappable character。 - 依赖坐标(关键):固定写旧 groupId
commons-jxpath:commons-jxpath:1.4.0;org.apache.commons下无此构件。版本升级时只需改<version>一行,1.4.0 之后再无历史包袱版本。 - optional 依赖按需引入:使用 JDOM 文档模型等扩展特性时,再在工程里显式添加
org.jdom:jdom:1.1.3或commons-beanutils:commons-beanutils:1.10.1;默认的 Bean/DOM 导航不需要它们(见 4.5 节 POM 解读)。 - 日志:1.4.0 的 POM 仅在
dependencyManagement中锁定commons-logging:1.3.5的版本。基本用法不强制该 jar(4.6/5.4 节实测运行期无它也不报错);若宿主应用需要日志输出,自行在依赖中添加commons-logging即可。
八、常见问题(FAQ)
Q1:执行 XPath 报 JXPathInvalidSyntaxException: Invalid XPath: 'employees[1].name' 怎么办?
JXPath 的 XPath 方言不支持点式续写:谓词方括号或下标之后必须用 / 访问子属性。实测报错:
getValue("employees[1].name") threw org.apache.commons.jxpath.JXPathInvalidSyntaxException
message: Invalid XPath: 'employees[1].name'. Syntax error after: 'employees[1]'
把点改成斜杠即可:
getValue("employees[1]/name") = Alice
注意这条规则与 BeanUtils 的 employees[1].name 属性路径写法不同,从 BeanUtils 迁移过来的代码要逐一改写。
Q2:按 org.apache.commons:commons-jxpath 引依赖报找不到构件怎么办?
中央仓库该路径下没有 JXPath 构件(404)。JXPath 至今沿用旧 groupId,正确坐标为 commons-jxpath:commons-jxpath:1.4.0。可用第二章的连通性预检命令验证:旧坐标 POM URL 返回 200;把 URL 中的 commons-jxpath/commons-jxpath 换成 org/apache/commons/commons-jxpath 再探测即无此构件。1.3→1.4.0 跨代升级时 groupId 也不用变。
Q3:createPathAndSetValue 抛 Factory is not set on the JXPathContext 怎么办?
按路径创建对象前必须先调用 ctx.setFactory 注册 AbstractFactory 实现。实测报错与修复后的输出:
without AbstractFactory threw org.apache.commons.jxpath.JXPathException
root cause: Factory is not set on the JXPathContext - cannot create path: /employees[3]/address
with AbstractFactory, Carol now -> Employee{name='Carol', age=41, address=Address{city='Shenzhen'}}
工厂实现的 createObject 里按 parent 与 name 判断要创建哪类子对象(4.6 节 BeanNav 第 5 组用例有完整代码)。单纯的 setValue(不创建新对象)不需要工厂。
Q4:二进制包里没有 commons-logging,运行会报 NoClassDefFoundError 吗?
不会。1.4.0 对 commons-logging 只是版本管理声明,不是强制依赖。4.6 节 CP= 行显示运行期 classpath 仅一个 jar,5.4 节纯 jar 编译运行全程无任何类缺失报错。只有当宿主应用自身需要日志门面时才自行引入。
Q5:下载时提示 wget: command not found 怎么办?
最小化安装的系统可能没有预装 wget,实测现象:
bash: line 1: wget: command not found
ls: cannot access '/opt/commons-jxpath-1.4.0-bin.tar.gz': No such file or directory
改用 curl -sO <URL>(或先 apt-get install -y wget)即可,5.2 节的下载命令即为 curl 版本。
九、卸载与清理
JXPath 为纯 Java 库,不做系统级安装,删除工程目录、发行包与本地仓库构件即可完全清理:
rm -rf /opt/jxpath-demo /opt/jxpath-dist /opt/jxpath-dist-demo /opt/commons-jxpath-1.4.0-bin.tar.gz
rm -rf /root/.m2/repository/commons-jxpath
ls -d /opt/jxpath-demo /opt/jxpath-dist /opt/jxpath-dist-demo /root/.m2/repository/commons-jxpath 2>/dev/null || echo "jxpath all removed"
输出(目录与构件均已删除时):
jxpath all removed
⚠️ 注意:JDK 与 Maven 属基础环境,不随本文卸载;如需一并移除,用
apt-get remove -y openjdk-17-jdk maven并自行确认影响范围。
十、参考链接
- Apache Commons JXPath 官方主页:https://commons.apache.org/proper/commons-jxpath/
- 官方发行下载目录:https://downloads.apache.org/commons/jxpath/
- Maven 中央仓库坐标目录(旧 groupId):https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/
- JXPath 用户指南(User Guide):https://commons.apache.org/proper/commons-jxpath/userguide.html
- JXPath JavaDoc API 文档:https://commons.apache.org/proper/commons-jxpath/apidocs/index.html