Apache Commons JXPath 1.4.0 常用 API 与 XPath 语法详解
详解 JXPath 1.4.0 核心 API 与 Bean/XML 两类 XPath 导航语法:getValue、谓词、iteratePointers、setValue、createPathAndSetValue、变量、自定义函数、命名空间与相对上下文陷阱,每类示例均附真实运行回显。
Apache Commons JXPath 1.4.0 常用 API 与 XPath 语法详解
目标:系统梳理 Apache Commons JXPath 1.4.0 的核心 API 与两类导航语法——JavaBean 对象图导航与 XML(DOM)导航,逐类给出可直接复制的示例代码与实测回显,并沉淀两类上下文的行为差异与高频陷阱。以下全部代码与输出均在 Ubuntu 22.04(x86_64,JDK 17.0.20.1 + Maven 3.6.3)实测通过。
一、环境信息
本节先固定本次实测所用软硬件基线,便于读者逐项对照复现。
- 操作系统:Ubuntu 22.04(x86_64)
- JDK:OpenJDK 17.0.20.1(
java -version实测) - 构建工具:Apache Maven 3.6.3(
mvn -v实测) - 目标库:Apache Commons JXPath 1.4.0(Maven 坐标
commons-jxpath:commons-jxpath:1.4.0,旧 groupId) - 示例工程目录:
/opt/jxpath-demo - 源码文件:
src/main/java/demo/BeanNav.java(Bean 导航)、src/main/java/demo/XmlNav.java(XML 导航) - 运行期 classpath 文件:
/tmp/cp.txt(由mvn dependency:build-classpath生成) - 运行期 classpath 实测内容:仅
commons-jxpath-1.4.0.jar一个 jar(无任何传递依赖)
二、API 与语法总览
JXPath 的入口是 JXPathContext:用 JXPathContext.newContext(contextBean) 包住任意 JavaBean 或 DOM Document,之后即可用 XPath 1.0 表达式在对象图上取值、遍历、修改与按路径创建。同一套表达式语法在两类上下文中含义不同:Bean 上下文里「元素名」对应 getter/setter 属性、集合属性按下标访问;XML 上下文里则是元素、属性与命名空间。常用 API 一览:
| API | 作用 | 返回 | 实测要点 |
|---|---|---|---|
JXPathContext.newContext(bean) | 创建上下文 | JXPathContext | Bean 与 DOM Document 均可作为上下文对象 |
getValue(xpath) | 求值取第一个匹配 | Object | 多值时只取首个;谓词后必须用 / 续写 |
selectNodes(xpath) | 求值取全部匹配 | List | Bean 上下文返回属性值本身;XML 上下文返回 DOM Element |
iterate(xpath) | 遍历集合成员 | Iterator | Bean 上下文返回对象本身 |
iteratePointers(xpath) | 遍历命中位置 | Iterator(Pointer) | asPath() 给出规范路径;null 中间属性不产生 Pointer |
getPointer(xpath) | 取单个位置 | Pointer | XML 相对上下文要用 pointer.getNode() 而非 getValue() |
setValue(xpath, value) | 修改既有位置 | void | 直接改写原对象;XPath 下标 1-based |
createPathAndSetValue(xpath, value) | 创建缺失路径并赋值 | void | 必须先 setFactory(AbstractFactory) |
getVariables().declareVariable(name, value) | 声明变量 | void | 表达式中以 $name 引用 |
registerNamespace(prefix, uri) | 注册命名空间 | void | namespace-aware DOM 下文档内声明的前缀可自动解析 |
setFunctions(ClassFunctions) | 注册扩展函数 | void | 前缀:静态方法名(参数) 形式调用 |
💡 技术拓展:「对象图导航/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 年恢复维护。
三、前置条件
- JDK 17 与 Maven 已安装(安装步骤见《Ubuntu 22.04 安装 Apache Commons JXPath 指南》)。
- 工程已引入 JXPath 依赖——
/opt/jxpath-demo/pom.xml中的依赖声明:
<dependencies>
<dependency>
<groupId>commons-jxpath</groupId>
<artifactId>commons-jxpath</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
- 生成运行期 classpath 文件并核实其内容(后续所有示例都用它直跑):
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)
输出(-q 静默模式下无其他输出,CP= 即 classpath):
CP=/root/.m2/repository/commons-jxpath/commons-jxpath/1.4.0/commons-jxpath-1.4.0.jar
classpath 仅有 JXPath 一个 jar,说明 1.4.0 无强制传递依赖,运行期不需要额外准备任何构件。编译产物在 target/classes,运行命令统一为 java -cp target/classes:$(cat /tmp/cp.txt) 类名。
四、Bean 对象图导航详解
本章以一个 Company/Employee 两层对象图为样本,逐类演示 Bean 上下文的语法。对象图为:Company 有 name 属性与 employees 列表(Alice 28 岁/北京、Bob 35 岁/上海、Carol 41 岁/address 为 null)。以下代码片段均取自 demo/BeanNav.java,回显取自同一次真实运行。
4.1 基本取值 getValue 与点式写法陷阱
创建上下文后用 getValue 求值:直接属性、带下标的集合成员、谓词过滤各来一例:
JXPathContext ctx = JXPathContext.newContext(company);
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"));
输出:
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
要点:employees[1]/name 取到 Alice——XPath 下标是 1-based,employees[1] 对应 List.get(0);谓词 age > 30 命中多人时 getValue 只返回首个 Bob。最大陷阱是点式写法:employees[1].name 直接抛 JXPathInvalidSyntaxException,谓词或下标之后必须用 / 续写属性,这条与 BeanUtils 的点式属性路径不同。
4.2 selectNodes 全量匹配
getValue 只给首个匹配,要取全部匹配用 selectNodes:
List<?> names = ctx.selectNodes("employees[age > 30]/name");
System.out.println(" selectNodes(employees[age > 30]/name).size() = " + names.size() + ", names = " + names);
输出:
selectNodes(employees[age > 30]/name).size() = 2, names = [Bob, Carol]
要点:同一个表达式,getValue 给首个(Bob)、selectNodes 给全部([Bob, Carol])。注意 Bean 上下文里列表元素是属性值本身(字符串),与 XML 上下文返回 DOM Element 完全不同(见第五章 5.2 与第六章对照表)。
4.3 iterate 遍历集合
iterate 对集合表达式返回成员迭代器,Bean 上下文中迭代出的就是对象本身:
Iterator<?> it = ctx.iterate("/employees");
while (it.hasNext()) {
System.out.println(" iterate -> " + it.next());
}
输出:
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 个 Employee 对象全部遍历出,包括 address 为 null 的 Carol。适合「按条件圈定一批对象再逐个处理」的场景。
4.4 iteratePointers 与 Pointer
iteratePointers 迭代的是 Pointer——带位置信息的引用,asPath() 给出从根到该位置的规范路径,getValue() 给出该位置的值:
Iterator<?> ptrIt = ctx.iteratePointers("employees/address/city");
while (ptrIt.hasNext()) {
Pointer p = (Pointer) ptrIt.next();
System.out.println(" path=" + p.asPath() + " value=" + p.getValue());
}
输出:
path=/employees[1]/address/city value=Beijing
path=/employees[2]/address/city value=Shanghai
要点:表达式中 Carol 的 address 为 null,JXPath 跳过该分支不产生 Pointer,因此只有 2 条结果而不是 3 条。asPath() 的输出(如 /employees[1]/address/city)可直接回填给 getValue/setValue 使用,是定位与回写的好搭档。
4.5 setValue 修改对象图
setValue 按表达式定位并赋值,直接改写原对象:
ctx.setValue("employees[2]/age", 36);
System.out.println(" after setValue(employees[2]/age, 36) -> " + company.getEmployees().get(1));
输出:
after setValue(employees[2]/age, 36) -> Employee{name='Bob', age=36, address=Address{city='Shanghai'}}
要点:employees[2] 命中 Bob(1-based 下标),赋值后原对象图里的 Bob 年龄从 35 变为 36——JXPath 修改的就是业务对象本身,无需再手动回拷。setValue 只能修改已存在的位置;要创建缺失路径用 4.6 节的 createPathAndSetValue。
4.6 createPathAndSetValue 与 AbstractFactory
createPathAndSetValue 会把路径上缺失的中间对象创建出来再赋值。Bean 上下文中必须先注册工厂,否则报错;注册后自动创建 null 属性:
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));
}
输出:
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'}}
要点:未设工厂时抛 JXPathException,根因为 Factory is not set on the JXPathContext - cannot create path: /employees[3]/address;setFactory 注册的 createObject 按「父对象类型 + 属性名」判断要 new 什么(本例为 Employee 的 address 属性创建 Address 实例),随后同一表达式成功把 Carol 的 city 赋为 Shenzhen。单纯的 setValue(不创建新对象)不需要工厂。
4.7 变量声明与 $var 引用
表达式中的 $name 引用上下文变量,变量通过 getVariables().declareVariable 声明:
ctx.getVariables().declareVariable("minAge", Integer.valueOf(30));
Iterator<?> seniors = ctx.iterate("employees[age > $minAge]/name");
while (seniors.hasNext()) {
System.out.println(" senior = " + seniors.next());
}
输出:
senior = Bob
senior = Carol
要点:$minAge 在谓词中参与比较(age > $minAge),命中 Bob 与 Carol。把阈值等参数从表达式硬编码改为变量注入,表达式即可复用。
4.8 自定义函数 ClassFunctions
setFunctions 注册 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)"));
输出:
util:reverse(name) = proC emcA
util:charCount(name) = 9
要点:util:reverse(name) 把上下文 Bean(Company)的 name 属性值 Acme Corp 反转为 proC emcA,util:charCount(name) 返回长度 9。ClassFunctions(类, 前缀) 把该类全部静态方法挂到指定前缀下,是扩展 XPath 内置函数(如 sum())之外业务函数的标准途径。
五、XML(DOM)导航详解
本章用一段内嵌的 library/book XML 演示 DOM 上下文语法。文档根元素 library 带命名空间声明 xmlns:d="http://demo/ns/discount",shelf id="A" 下有 3 个 book(第一本带 d:discount 子元素 0.8,三本价格均为 USD 计价的 39.99/32.50/45.00)。以下代码片段取自 demo/XmlNav.java,回显取自同一次真实运行。
5.1 构建 DOM 上下文与绝对路径取值
用 JDK 内置解析器构建 Document(开启 namespace-aware),直接作为上下文对象:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder()
.parse(new ByteArrayInputStream(XML.getBytes(StandardCharsets.UTF_8)));
JXPathContext ctx = JXPathContext.newContext(doc);
System.out.println(" /library/shelf/book[1]/title = " + ctx.getValue("/library/shelf/book[1]/title"));
System.out.println(" /library/shelf/book/author = " + ctx.getValue("/library/shelf/book/author"));
输出:
/library/shelf/book[1]/title = Java Concurrency in Practice
/library/shelf/book/author = Brian Goetz
要点:JXPathContext.newContext(document) 原生支持 DOM Document,无需任何适配器;绝对路径、下标(1-based)与标准 XPath 语义一致;XML 元素上 getValue 返回的是字符串文本(与 Bean 上下文返回对象本身不同)。
5.2 selectNodes 返回的是 DOM Element
XML 上下文中 selectNodes 的元素与 Bean 上下文不同——返回的是 DOM Element 对象,不是字符串:
List<?> titles = ctx.selectNodes("//book/title");
for (Object t : titles) {
System.out.println(" item class = " + t.getClass().getName() + ", toString = " + t);
if (t instanceof Pointer) {
Pointer p = (Pointer) t;
System.out.println(" asPath=" + p.asPath() + " value=" + p.getValue());
}
if (t instanceof org.w3c.dom.Element) {
System.out.println(" as Element, getTextContent() = " + ((org.w3c.dom.Element) t).getTextContent());
}
}
输出:
item class = com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl, toString = [title: null]
as Element, getTextContent() = Java Concurrency in Practice
item class = com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl, toString = [title: null]
as Element, getTextContent() = Clean Code
item class = com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl, toString = [title: null]
as Element, getTextContent() = Effective Java
要点:3 个结果的实际类型是 com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl(JDK 内置 Xerces 实现的 Element,这也印证了 JXPath 1.4.0 不带 xerces 依赖、直接用 JDK 内置实现),toString 形如 [title: null] 并非文本内容,循环里的 instanceof Pointer 分支也未命中(结果既不是 Pointer)。取文本必须转型 Element 后调 getTextContent();也说明 XML 解析走的是 JDK 内置实现,classpath 不需要任何 XML 库。
5.3 iteratePointers 遍历节点与 asPath
对节点集合迭代 Pointer,asPath() 给出每个节点的规范路径:
Iterator<?> pit = ctx.iteratePointers("//book");
while (pit.hasNext()) {
Pointer p = (Pointer) pit.next();
System.out.println(" " + p.asPath());
}
输出:
/library[1]/shelf[1]/book[1]
/library[1]/shelf[1]/book[2]
/library[1]/shelf[1]/book[3]
要点://book 深度搜索命中 3 个 book 节点;asPath() 输出的是全量限定路径(每级都带 [1] 序号),可直接回填给 getPointer/setValue 做精确定位。
5.4 谓词、属性与函数
XML 上下文完整支持谓词比较、@属性 访问与 XPath 内置函数:
System.out.println(" //book[price > 40]/title = " + ctx.getValue("//book[price > 40]/title"));
System.out.println(" //book[2]/price/@currency = " + ctx.getValue("//book[2]/price/@currency"));
System.out.println(" sum(//book/price) = " + ctx.getValue("sum(//book/price)"));
输出:
//book[price > 40]/title = Effective Java
//book[2]/price/@currency = USD
sum(//book/price) = 117.49000000000001
要点:谓词 price > 40 按子元素文本做数值比较命中 Effective Java;@currency 取属性值 USD;sum() 对 3 个价格(39.99 + 32.50 + 45.00)求和。结果 117.49000000000001 是双精度浮点累加的正常误差(XPath 数值即 double 语义),业务上需要精确金额时应取值后用 BigDecimal 计算,而不是指望 sum() 输出精确十进制。
5.5 命名空间处理
对 namespace-aware 构建的 DOM,文档内已声明的前缀可直接使用;再显式 registerNamespace 结果一致:
System.out.println(" before register: //book/d:discount = " + ctx.getValue("//book/d:discount"));
ctx.registerNamespace("d", "http://demo/ns/discount");
System.out.println(" after register: //book/d:discount = " + ctx.getValue("//book/d:discount"));
输出:
before register: //book/d:discount = 0.8
after register: //book/d:discount = 0.8
要点:两次都成功取到 0.8——namespace-aware DOM 下,文档内 xmlns:d 声明的前缀自动解析,无需预注册;registerNamespace 用于文档内没有前缀声明(或前缀与文档不一致)的场景,注册后两者等价。注意前提是 DocumentBuilderFactory.setNamespaceAware(true),否则 DOM 不保留命名空间信息。
5.6 相对上下文与 getNode 陷阱
从 Pointer 取出节点再建子上下文时,必须用 pointer.getNode();用 pointer.getValue() 会拿到文本聚合值并导致后续求值失败:
Pointer bookPtr = ctx.getPointer("//book[2]");
try {
JXPathContext viaValue = JXPathContext.newContext(ctx, bookPtr.getValue());
System.out.println(" via getValue(): title = " + viaValue.getValue("title"));
} catch (Exception e) {
System.out.println(" via getValue() threw " + e.getClass().getName());
System.out.println(" message: " + e.getMessage());
}
JXPathContext relCtx = JXPathContext.newContext(ctx, bookPtr.getNode());
System.out.println(" via getNode(): title = " + relCtx.getValue("title"));
System.out.println(" via getNode(): price/@currency = " + relCtx.getValue("price/@currency"));
输出:
via getValue() threw org.apache.commons.jxpath.JXPathNotFoundException
message: No value for xpath: 'Clean CodeRobert C. Martin32.50'/title
via getNode(): title = Clean Code
via getNode(): price/@currency = USD
要点:DOM 元素上 pointer.getValue() 返回的是全部文本聚合值(把 book 下 title、author、price 的文本拼接成 Clean CodeRobert C. Martin32.50),拿它当上下文对象自然查不到 title;改用 pointer.getNode() 取原始 Element 后,相对路径 title、price/@currency 全部正常。这是 XML 相对上下文最典型的坑,Bean 上下文没有此问题(Pointer 的 getValue() 就是属性值)。
六、Bean 与 XML 上下文行为差异对照
两类上下文共用一套表达式语法,但求值结果的「形态」不同,混用时务必对照下表:
| 行为 | Bean 上下文 | XML(DOM)上下文 |
|---|---|---|
| getValue 命中多值 | 返回首个匹配的属性值(如 employees[age > 30]/name 返回 Bob) | 返回首个匹配元素的文本(如 //book[price > 40]/title 返回 Effective Java) |
| selectNodes 返回类型 | 属性值本身(实测 [Bob, Carol] 字符串列表) | DOM Element 对象(DeferredElementNSImpl),取文本用 getTextContent() |
| iterate 返回内容 | 集合成员对象本身(Employee 实例) | 节点对应值(元素为文本) |
| iteratePointers 遇 null 中间属性 | 跳过该分支(Carol 的 address=null 不产生 Pointer,仅 2 条) | DOM 节点均存在,无此现象(3 条 book) |
点式写法(employees[1].name) | 抛 JXPathInvalidSyntaxException,必须写 / | 路径写法本就是 / 分隔,无此坑 |
| Pointer 取节点建子上下文 | getValue() 即属性值,可直接用 | 必须用 getNode() 取原始 Element(getValue() 是文本聚合值) |
解读:差异集中在「结果形态」上——Bean 上下文面向 Java 对象,返回值就是对象或属性值;XML 上下文面向节点树,selectNodes 给节点、getValue 给文本。写通用工具方法时要对返回类型做 instanceof 分支处理(如同时兼容 String 与 Element),或按上下文类型分别封装。
七、注意事项
- 谓词/下标后必须用
/续写:employees[1].name抛JXPathInvalidSyntaxException,正确写法employees[1]/name;从 BeanUtils 点式路径迁移时逐一改写。 - XPath 下标 1-based:
employees[1]对应List.get(0),与 Java 习惯相反。 getValue只取首个匹配:要多值用selectNodes(Bean 上下文给值列表、XML 上下文给 Element 列表)或iterate/iteratePointers。createPathAndSetValue前必须setFactory:否则抛JXPathException: Factory is not set on the JXPathContext;工厂按「父对象 + 属性名」创建缺失对象。- XML 相对上下文用
getNode():pointer.getValue()对元素返回文本聚合值,会导致子上下文求值JXPathNotFoundException。 - XML 取文本转型 Element:
selectNodes在 XML 上下文返回DeferredElementNSImpl,取文本用getTextContent()。 - 数值函数是 double 语义:
sum(//book/price)实测117.49000000000001,金额类需求取值后用BigDecimal。 - 运行期零负担:classpath 仅
commons-jxpath-1.4.0.jar一个 jar,XML 解析使用 JDK 内置 Xerces 实现,无需额外 XML 库。
八、常见问题(FAQ)
Q1:报 Invalid XPath: 'employees[1].name'. Syntax error after: 'employees[1]' 怎么办?
点式续写不被支持,谓词或下标之后用 /。实测报错与改写后的结果:
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
Q2:报 Factory is not set on the JXPathContext - cannot create path 怎么办?
createPathAndSetValue 前未注册工厂。注册 AbstractFactory 并在 createObject 中按父对象与属性名创建子对象后即可成功,实测对照回显:
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'}}
Q3:XML 上 selectNodes 的结果 toString 打出来是 [title: null],取不到文字?
返回的是 DOM Element 而非字符串。转型 org.w3c.dom.Element 后调 getTextContent(),实测:
item class = com.sun.org.apache.xerces.internal.dom.DeferredElementNSImpl, toString = [title: null]
as Element, getTextContent() = Java Concurrency in Practice
Q4:报 No value for xpath: 'Clean CodeRobert C. Martin32.50'/title 怎么办?
用 pointer.getValue() 的返回值(文本聚合值)建了子上下文。改用 pointer.getNode() 取原始 Element,实测:
via getValue() threw org.apache.commons.jxpath.JXPathNotFoundException
message: No value for xpath: 'Clean CodeRobert C. Martin32.50'/title
via getNode(): title = Clean Code
via getNode(): price/@currency = USD
Q5:sum(//book/price) 结果是 117.49000000000001,是不是算错了?
没有算错:XPath 数值是 double 语义,39.99 + 32.50 + 45.00 的双精度累加产生二进制浮点表示误差。实测:
sum(//book/price) = 117.49000000000001
需要精确十进制时,取各价格值后用 BigDecimal 自行累加。
九、参考链接
- JXPath 用户指南(User Guide,含完整语法说明):https://commons.apache.org/proper/commons-jxpath/userguide.html
- JXPath JavaDoc API 文档(JXPathContext/Pointer 等):https://commons.apache.org/proper/commons-jxpath/apidocs/index.html
- Apache Commons JXPath 官方主页:https://commons.apache.org/proper/commons-jxpath/
- Maven 中央仓库坐标目录(旧 groupId):https://repo.maven.apache.org/maven2/commons-jxpath/commons-jxpath/
- 升级与安装环境搭建见《Ubuntu 22.04 安装 Apache Commons JXPath 指南》