Java环境下XPath表达式无法获取CRM_Company节点列表问题
XPath匹配SOAP响应中CRM_Company节点失败的修复方案
问题根因
代码无法拿到目标节点,是三个问题叠加导致的:
- 自定义
NamespaceResolver存在空指针风险:按照JAXP的NamespaceContext规范,实现类必须处理入参prefix为null的场景,返回空命名空间URI。当前代码直接执行prefix.equals(...)判断,当XPath引擎传入null前缀查询上下文时,会触发空指针。部分XPath实现会吞掉解析阶段的该类异常,直接返回空节点集,这是拿不到匹配结果的核心原因。 NamespaceResolver不符合接口规范:接口强制要求xml前缀必须映射到http://www.w3.org/XML/1998/namespace、xmlns前缀必须映射到http://www.w3.org/2000/xmlns/,当前实现对这两个前缀返回null,会导致XPath解析上下文异常。另外依赖Document.lookupNamespaceURI()做查询是不可靠的——该方法从调用节点沿父链向上查找命名空间声明,Document根节点无法感知子节点上声明的默认命名空间,会返回错误的null值。- 节点取值逻辑错误:就算XPath成功匹配到节点,DOM规范中
Element类型节点的getNodeValue()方法固定返回null,无法通过该方法拿到节点内容。
修复方案
1. 重写稳定的NamespaceResolver实现
不要依赖Document的动态命名空间查找,直接硬绑定需要用到的命名空间映射,同时覆盖规范要求的所有场景:
import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import java.util.*; public class NamespaceResolver implements NamespaceContext { private final Map<String, String> prefixUriMap = new HashMap<>(); public NamespaceResolver() { // 绑定自定义前缀到CRM服务的命名空间 prefixUriMap.put("ns", "urn:microsoft-dynamics-schemas/page/crm_company"); // 绑定规范要求的固定前缀映射 prefixUriMap.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI); prefixUriMap.put(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI); prefixUriMap.put(XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI); } @Override public String getNamespaceURI(String prefix) { if (prefix == null) { return XMLConstants.NULL_NS_URI; } return prefixUriMap.getOrDefault(prefix, XMLConstants.NULL_NS_URI); } @Override public String getPrefix(String namespaceUri) { if (namespaceUri == null) { return null; } for (Map.Entry<String, String> entry : prefixUriMap.entrySet()) { if (entry.getValue().equals(namespaceUri)) { return entry.getKey(); } } return null; } @Override @SuppressWarnings("rawtypes") public Iterator getPrefixes(String namespaceUri) { String prefix = getPrefix(namespaceUri); if (prefix == null) { return Collections.emptyIterator(); } return Collections.singletonList(prefix).iterator(); } }
2. 修正节点取值逻辑
将遍历节点的代码从调用getNodeValue()改为调用getTextContent(),如果需要获取No、Name等子节点字段可以进一步遍历子节点:
NodeList nodeList = (NodeList) xPath.compile("//ns:CRM_Company").evaluate(xmlDocument, XPathConstants.NODESET); Vector<String> ret = new Vector<>(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); // 元素节点通过getTextContent获取文本内容 ret.add(node.getTextContent()); } System.out.println(ret);
替换后即可正常匹配到所有CRM_Company节点。
内容的提问来源于stack exchange,提问作者Sven
相关产品推荐
相关产品推荐

