1. 概述

在设计数据交换的API时,我们通常采用REST或SOAP架构风格。在使用SOAP协议时,有时需要从SOAP消息中提取特定数据以便后续处理。

本教程将学习如何在Java中提取SOAP消息的特定部分。

2. SOAPMessage类详解

在深入之前,先快速了解*SOAPMessage*类的结构,它是所有SOAP消息的根类:

SOAPMessage类结构

该类包含两个主要部分: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获取。


原始标题:Get Specific Part From SOAP Message in Java | Baeldung