Ubuntu 22.04 安装 Apache Commons JXPath 指南

在 Ubuntu 22.04 上从零引入 Apache Commons JXPath 1.4.0:旧 groupId 选型要点、Maven 在线依赖与官方二进制包两种安装方案、依赖树核实与 Bean 对象图导航运行验证,全部命令附真实回显。

Apache Commons JXPathApache Commons JXPath2026/10/14

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」。

二、前置条件

在开始安装前,请确认以下条件满足,否则后续步骤会因权限或网络问题中断。

  1. root 权限或 sudo:需要在 /opt 下创建目录并写入文件。
  2. 网络可达:需要访问 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 可用)。

  1. 磁盘空间:官方二进制包实测 3,374,636 字节(约 3.4 MB),加上 Maven 本地仓库缓存建议预留 ≥ 200 MB。
  2. 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 JXPathXPath 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 并自行确认影响范围。

十、参考链接

  1. Apache Commons JXPath 官方主页:https://commons.apache.org/proper/commons-jxpath/
  2. 官方发行下载目录:https://downloads.apache.org/commons/jxpath/
  3. Maven 中央仓库坐标目录(旧 groupId):https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/
  4. JXPath 用户指南(User Guide):https://commons.apache.org/proper/commons-jxpath/userguide.html
  5. JXPath JavaDoc API 文档:https://commons.apache.org/proper/commons-jxpath/apidocs/index.html