1. 概述
在设计数据交换的API时,我们通常采用REST或SOAP架构风格。在使用SOAP协议时,有时需要从SOAP消息中提取特定数据以便后续处理。
本教程将学习如何在Java中提取SOAP消息的特定部分。
2. SOAPMessage类详解
在深入之前,先快速了解*SOAPMessage*类的结构,它是所有SOAP消息的根类:
该类包含两个主要部分:SOAP部分和可选的附件部分。前者包含SOAP信封(Envelope),其中保存着我们接收到的实际消息。此外,信封本身由头部(Header)和主体(Body)元素组成。
⚠️ 从Java 11开始,Java EE(包括JAX-WS和SAAJ模块)已从JDK中移除。要在Jakarta EE 9及以上版本中处理SOAP消息,需在pom.xml中添加以下依赖:
<dependency>
<groupId>jakarta.xml.soap</groupId>
<artifactId>jakarta.xml.soap-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.messaging.saaj</groupId>
<artifactId>saaj-impl</artifactId>
<version>3.0.3</version>
</dependency>
3. 工作示例
接下来创建本教程将使用的XML消息:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:be="http://www.baeldung.com/soap/">
<soapenv:Header>
<be:Username>baeldung</be:Username>
</soapenv:Header>
<soapenv:Body>
<be:ArticleRequest>
<be:Article>
<be:Name>Working with JUnit</be:Name>
</be:Article>
</be:ArticleRequest>
</soapenv:Body>
</soapenv:Envelope>
4. 从SOAP消息提取头部和主体
现在来看如何从SOAP消息中提取头部和主体元素。
根据SOAPMessage类层次结构,要获取实际SOAP消息,需先获取SOAP部分,再获取信封:
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("soap-message.xml");
SOAPMessage soapMessage = MessageFactory.newInstance().createMessage(new MimeHeaders(), inputStream);
SOAPPart part = soapMessage.getSOAPPart();
SOAPEnvelope soapEnvelope = part.getEnvelope();
获取头部元素只需调用*getHeader()*方法:
SOAPHeader soapHeader = soapEnvelope.getHeader();
类似地,通过*getBody()*方法提取主体元素:
SOAPBody soapBody = soapEnvelope.getBody();
5. 从SOAP消息提取特定元素
掌握了基础元素提取后,现在探索如何提取SOAP消息的特定部分。
5.1. 按标签名获取元素
*可使用getElementsByTagName()方法获取特定元素**。该方法返回NodeList,其中Node是所有DOM组件的基础数据类型——即所有元素、属性和文本内容都被视为Node*类型。
从XML中提取Name元素:
@Test
void whenGetElementsByTagName_thenReturnCorrectBodyElement() throws Exception {
SOAPEnvelope soapEnvelope = getSoapEnvelope();
SOAPBody soapBody = soapEnvelope.getBody();
NodeList nodes = soapBody.getElementsByTagName("be:Name");
assertNotNull(nodes);
Node node = nodes.item(0);
assertNotNull(node);
assertEquals("Working with JUnit", node.getTextContent());
}
⚠️ 注意:必须向方法传递命名空间前缀才能正确工作。
同样方法可用于获取SOAP头部元素:
@Test
void whenGetElementsByTagName_thenReturnCorrectHeaderElement() throws Exception {
SOAPEnvelope soapEnvelope = getSoapEnvelope();
SOAPHeader soapHeader = soapEnvelope.getHeader();
NodeList nodes = soapHeader.getElementsByTagName("be:Username");
assertNotNull(nodes);
Node node = nodes.item(0);
assertNotNull(node);
assertEquals("baeldung", node.getTextContent());
}
5.2. 遍历子节点
另一种获取特定元素值的方式是遍历子节点。
演示如何遍历主体元素的子节点:
@Test
void whenGetElementUsingIterator_thenReturnCorrectBodyElement() throws Exception {
SOAPEnvelope soapEnvelope = getSoapEnvelope();
SOAPBody soapBody = soapEnvelope.getBody();
NodeList childNodes = soapBody.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if ("Name".equals(node.getLocalName())) {
String name = node.getTextContent();
assertEquals("Working with JUnit", name);
}
}
}
5.3. 使用XPath
接下来看如何使用XPath提取元素。简单说,XPath是描述XML部分的语法。它通过XPath表达式工作,可用于检索满足特定条件的元素。
首先创建XPath实例:
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
为有效处理命名空间,定义命名空间上下文:
xpath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
if ("be".equals(prefix)) {
return "http://www.baeldung.com/soap/";
}
return null;
}
// 其他方法
});
这样XPath就知道去哪里查找数据。
然后定义提取Name元素值的XPath表达式:
XPathExpression expression = xpath.compile("//be:Name/text()");
这里结合了路径表达式和返回节点文本内容的*text()*函数。
最后调用*evaluate()*方法获取匹配结果:
String name = (String) expression.evaluate(soapBody, XPathConstants.STRING);
assertEquals("Working with JUnit", name);
此外,可创建忽略命名空间的表达式:
@Test
void whenGetElementUsingXPathAndIgnoreNamespace_thenReturnCorrectResult() throws Exception {
SOAPBody soapBody = getSoapBody();
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPathExpression expression = xpath.compile("//*[local-name()='Name']/text()");
String name = (String) expression.evaluate(soapBody, XPathConstants.STRING);
assertEquals("Working with JUnit", name);
}
使用local-name()函数忽略命名空间,因此表达式会选择所有本地名为Name的元素,不考虑命名空间前缀。
6. 总结
本文学习了如何在Java中提取SOAP消息的特定部分。
✅ 总结:从SOAP消息提取特定元素有多种方式,包括:
- 按标签名搜索元素
- 遍历子节点
- 使用XPath表达式
完整代码示例可在GitHub获取。