处理XML文档时,很多场景都需要从复杂的层级结构中抽取特定数据,比如解析配置文件、读取接口返回的SOAP报文、分析RSS订阅源等。如果用DOM API一层层调用getChildNodes()去遍历,代码既冗长又容易出错。XPath正是为解决这个问题而生的,它提供了一套类似路径的表达式语法,让开发者可以用一条语句直接描述目标节点的位置。Java官方在JDK中内置了javax.xml.xpath包,这篇文章就详细介绍它的完整用法。

一、XPath表达式基础:先搞懂路径怎么写
XPath的核心思想是用路径表达式描述节点位置,语法上和文件系统路径非常相似。绝对路径以斜杠开头,例如/books/book表示根元素books下的所有book子节点;相对路径则从当前节点出发,例如title表示当前节点的所有title子节点。
除了逐级定位,XPath还提供了几个非常实用的特殊记号。双斜杠//表示从文档中任意位置查找,例如//book会匹配文档里所有的book元素,不管它嵌套多深;@用于选取属性,例如//book/@id返回所有book节点的id属性值;*是通配符,/books/*匹配books下的所有子元素。
真正让XPath强大的是谓语(predicate),也就是方括号里的筛选条件。常见的写法有:
/books/book[1]:选取第一个book节点,注意XPath的索引从1开始,不是0。//book[@category='web']:选取category属性等于web的所有book节点。//book[price>35]:选取price子元素值大于35的book节点。//book[last()]:选取最后一个book节点。
掌握这几个基础语法,就能覆盖日常开发中九成以上的定位需求。如果需要更复杂的逻辑,还可以使用and、or组合多个条件,或者用text()直接匹配文本内容,例如//title[text()='Java编程']。
二、使用javax.xml.xpath执行查询的完整流程
javax.xml.xpath包的入口是XPathFactory,通过它的newXPath()方法可以创建一个XPath实例。整个查询流程分为三步:解析XML得到Document对象、编译XPath表达式、执行查询并处理结果。
下面是一个完整的示例,演示如何从一个书籍列表的XML中查询所有价格大于35元的书名:
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.*;
public class XPathDemo {
public static void main(String[] args) throws Exception {
// 第一步:解析XML文档
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder()
.parse("books.xml");
// 第二步:创建XPath对象并编译表达式
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
XPathExpression expr = xpath.compile("//book[price>35]/title");
// 第三步:执行查询
NodeList titles = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < titles.getLength(); i++) {
System.out.println(titles.item(i).getTextContent());
}
}
}代码中有几个细节值得注意。第一,compile()方法会把表达式编译成XPathExpression对象,如果同一条表达式要反复执行,提前编译能显著提升性能,因为编译过程本身是有开销的。第二,evaluate()的第二个参数指定返回类型,这里传的是XPathConstants.NODESET,表示返回节点集合。
对应的测试用XML文件大致如下:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<title>Java核心技术</title>
<price>89.00</price>
</book>
<book id="2">
<title>Python入门</title>
<price>29.00</price>
</book>
</books>运行上面的代码,输出结果是Java核心技术,因为只有第一本书的价格满足大于35这个条件。这个流程套用到任何XML文档上都成立,只需要替换表达式即可。
三、五种返回类型的选择与处理
evaluate()方法支持五种返回类型,分别对应XPathConstants中的五个常量,理解它们的区别是用好这个API的关键。
第一种是NUMBER,返回Double类型,适合做统计类查询。比如统计book节点的总数:
double count = (Double) xpath.evaluate("count(//book)", doc, XPathConstants.NUMBER);
System.out.println("书籍总数:" + (int) count);第二种是STRING,返回String类型,直接拿到文本内容,常用于提取单个属性或某个唯一节点的值:
String firstTitle = xpath.evaluate("//book[1]/title/text()", doc);
// 或者提取属性值
String id = xpath.evaluate("//book[1]/@id", doc);第三种是BOOLEAN,返回Boolean类型,配合exists类逻辑判断节点是否存在:
boolean exists = (Boolean) xpath.evaluate(
"boolean(//book[@category='tech'])", doc, XPathConstants.BOOLEAN);第四种和第五种分别是NODE和NODESET,返回org.w3c.dom.Node和NodeList。当你需要继续操作节点本身(比如读取子节点、修改属性)时就要用这两种类型。NODE适合确定只有一个匹配结果的场景,如果实际匹配到多个节点,只会返回第一个。需要特别留意的是,NodeList是即时结果的快照,如果查询之后又修改了DOM树,之前拿到的列表不会自动更新,需要重新执行查询。
四、命名空间与常见坑点
实际项目中XML文档往往带有命名空间,最典型的就是Spring的配置文件和SOAP报文。这时候直接用//bean这类表达式查不到任何节点,因为带命名空间的元素名称实际上是命名空间URI:本地名的组合,和表达式里的裸名称对不上。
解决思路是在解析时忽略命名空间,或者注册命名空间映射。忽略的方式更简单:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(false); // 不感知命名空间,直接按本地名匹配
如果必须保留命名空间感知,就得实现NamespaceContext接口,把前缀和URI的映射关系告诉XPath引擎:
xpath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
if ("ctx".equals(prefix)) return "http://www.ippipp.com/schema";
return null;
}
@Override
public String getPrefix(String namespaceURI) { return null; }
@Override
public Iterator getPrefixes(String namespaceURI) {
return Collections.emptyList().iterator();
}
});
// 查询时使用带前缀的表达式
NodeList nodes = (NodeList) xpath.evaluate(
"//ctx:book", doc, XPathConstants.NODESET);除了命名空间,还有几个容易踩的坑。一是索引从1开始,写成book[0]不会报错但永远返回空结果,非常隐蔽;二是表达式语法错误会抛出XPathExpressionException,建议把查询逻辑包在try-catch中统一处理;三是XPath对象本身不是线程安全的,多线程环境下应该为每个线程创建独立实例,或者使用ThreadLocal来隔离。另外,如果XML来源不受信任,记得禁用外部实体解析,防止XXE注入攻击,可以在工厂上调用factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)来关闭DTD支持。
总结一下,javax.xml.xpath包的API设计相当精简,核心就是XPathFactory、XPath和XPathExpression三个类,配合五种返回类型就能应对绝大多数XML定位场景。熟练掌握XPath表达式语法,比记住API本身更重要,建议把常用的谓语筛选、函数调用整理成速查表,开发效率会明显提升。
Java XPathjavax.xml.xpathXML节点定位修改时间:2026-09-10 18:32:40