杰瑞科技汇

java解析soap的xml

SOAP 消息结构

理解 SOAP 消息的结构很重要,一个典型的 SOAP 消息如下:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <!-- 可选的头部信息 -->
  </soap:Header>
  <soap:Body>
    <m:GetStockPrice xmlns:m="http://www.example.org/stock">
      <m:StockName>IBM</m:StockName>
    </m:GetStockPrice>
  </soap:Body>
</soap:Envelope>
  • <Envelope>: 整个 SOAP 消息的根元素。
  • <Header>: 可选部分,用于包含认证、事务等元数据。
  • <Body>: 必需部分,包含实际的业务数据和请求/响应信息。

使用标准 Java 库 (JAXP - DOM/SAX) - 纯 Java 方案

这是最基础的方法,不依赖任何第三方库,适用于简单的 XML 解析任务。

DOM (Document Object Model) - 适合小文档

DOM 会将整个 XML 文档加载到内存中,解析成一个树形结构,这种方式非常直观,易于操作,但如果 XML 文件很大,会消耗大量内存。

示例代码:

import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
public class SoapDomParser {
    public static void parseSoapMessage(String soapXml) {
        try {
            // 1. 创建 DocumentBuilderFactory 和 DocumentBuilder
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            // 启用命名空间支持,这对于 SOAP XML 非常重要
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 2. 解析 XML 字符串
            Document document = builder.parse(new ByteArrayInputStream(soapXml.getBytes()));
            // 3. 获取根元素 (Envelope)
            Element envelope = document.getDocumentElement();
            System.out.println("Root Element: " + envelope.getNodeName());
            // 4. 获取 Body 元素
            NodeList bodyList = envelope.getElementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/", "Body");
            if (bodyList.getLength() > 0) {
                Element body = (Element) bodyList.item(0);
                System.out.println("Found Body Element.");
                // 5. 获取 Body 下的第一个子元素 ( GetStockPrice)
                NodeList children = body.getChildNodes();
                for (int i = 0; i < children.getLength(); i++) {
                    Node child = children.item(i);
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        Element operationElement = (Element) child;
                        System.out.println("Operation Element: " + operationElement.getLocalName());
                        // 6. 获取操作元素下的子元素 ( StockName)
                        NodeList stockNameList = operationElement.getElementsByTagNameNS("http://www.example.org/stock", "StockName");
                        if (stockNameList.getLength() > 0) {
                            Element stockNameElement = (Element) stockNameList.item(0);
                            String stockName = stockNameElement.getTextContent();
                            System.out.println("Stock Name: " + stockName);
                        }
                        break; // 假设我们只关心第一个操作
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        String soapXml = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
                "  <soap:Body>\n" +
                "    <m:GetStockPrice xmlns:m=\"http://www.example.org/stock\">\n" +
                "      <m:StockName>IBM</m:StockName>\n" +
                "    </m:GetStockPrice>\n" +
                "  </soap:Body>\n" +
                "</soap:Envelope>";
        parseSoapMessage(soapXml);
    }
}

SAX (Simple API for XML) - 适合大文档

SAX 是一种事件驱动的解析模型,它不会将整个文档加载到内存,而是当解析器遇到 XML 的开始标签、结束标签、文本内容等事件时,会调用相应的方法处理,它非常节省内存,但编程模型比 DOM 复杂。

示例代码:

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.ByteArrayInputStream;
public class SoapSaxParser {
    public static void parseWithSAX(String soapXml) {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(true); // 关键:启用命名空间
            SAXParser saxParser = factory.newSAXParser();
            DefaultHandler handler = new DefaultHandler() {
                boolean inBody = false;
                boolean inOperation = false;
                boolean inStockName = false;
                StringBuilder currentValue = new StringBuilder();
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) {
                    if ("Body".equals(localName) && "http://schemas.xmlsoap.org/soap/envelope/".equals(uri)) {
                        inBody = true;
                        System.out.println("Start Element: Body");
                    } else if (inBody && "GetStockPrice".equals(localName)) {
                        inOperation = true;
                        System.out.println("Start Element: Operation (" + localName + ")");
                    } else if (inOperation && "StockName".equals(localName) && "http://www.example.org/stock".equals(uri)) {
                        inStockName = true;
                        System.out.println("Start Element: StockName");
                    }
                }
                @Override
                public void characters(char[] ch, int start, int length) {
                    if (inStockName) {
                        currentValue.append(ch, start, length);
                    }
                }
                @Override
                public void endElement(String uri, String localName, String qName) {
                    if ("Body".equals(localName)) {
                        inBody = false;
                        System.out.println("End Element: Body");
                    } else if (inOperation && "GetStockPrice".equals(localName)) {
                        inOperation = false;
                        System.out.println("End Element: Operation");
                    } else if (inStockName && "StockName".equals(localName)) {
                        inStockName = false;
                        System.out.println("Stock Name: " + currentValue.toString().trim());
                        currentValue.setLength(0); // 清空 StringBuilder
                    }
                }
            };
            saxParser.parse(new ByteArrayInputStream(soapXml.getBytes()), handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        String soapXml = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
                "  <soap:Body>\n" +
                "    <m:GetStockPrice xmlns:m=\"http://www.example.org/stock\">\n" +
                "      <m:StockName>IBM</m:StockName>\n" +
                "    </m:GetStockPrice>\n" +
                "  </soap:Body>\n" +
                "</soap:Envelope>";
        parseWithSAX(soapXml);
    }
}

使用第三方库 (推荐)

对于复杂的 SOAP 交互,手动解析 XML 非常繁琐且容易出错,使用专门的 SOAP 库是更好的选择。

Apache Axis2 / Apache CXF (功能强大的 SOAP 框架)

这些是成熟的 Java Web Service 框架,它们不仅能生成客户端代码,也能让你以更面向对象的方式处理 SOAP 消息,你会使用它们来生成客户端 stub,然后直接调用方法,框架会自动处理序列化和反序列化。

场景:你已经有一个 WSDL (Web Services Description Language) 文件。

  1. 生成客户端代码: 使用 Axis2 或 CXF 的工具(如 wsdl2java)根据 WSDL 文件生成 Java 客户端代码。

    # Axis2 示例
    wsdl2java -uri http://www.example.org/stock?wsdl -p org.example.stock
  2. 使用生成的客户端: 生成的代码会包含一个 StockServiceStub 或类似的类,你可以直接调用其中的方法。

    // 假设生成的类名为 StockServiceStub
    StockServiceStub stub = new StockServiceStub();
    GetStockPrice request = new GetStockPrice();
    request.setStockName("IBM");
    GetStockPriceResponse response = stub.GetStockPrice(request);
    String price = response.getGetStockPriceResult();
    System.out.println("Stock Price: " + price);

    这种方式是最佳实践,因为它将 SOAP 的复杂性完全封装起来。

WSDL4J (轻量级 WSDL/SAX 解析器)

如果你只需要解析 WSDL 文件来获取服务信息(如端口、操作、参数等),而不想生成完整的客户端代码,WSDL4J 是一个很好的选择。

Woodstox (高性能的 StAX 解析器)

StAX (Streaming API for XML) 是介于 DOM 和 SAX 之间的一种解析方式,它允许你像 SAX 一样以流的方式读取 XML,但你可以主动控制解析器(pull 模式),而不是被动接收事件,Woodstox 是一个流行的高性能 StAX 实现。

示例代码 (使用 Woodstox 解析 SOAP Body):

import com.ctc.wstx.api.WstxInputProperties;
import com.ctc.wstx.stax.WstxInputFactory;
import org.codehaus.stax2.XMLStreamReader2;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
public class SoapStaxParser {
    public static void parseWithStax(String soapXml) throws XMLStreamException {
        // 使用 Woodstox 作为 StAX 实现
        XMLInputFactory factory = new WstxInputFactory();
        // 优化性能
        factory.setProperty(WstxInputProperties.P_MAX_ATTRIBUTE_SIZE, 20 * 1024);
        XMLStreamReader reader = factory.createXMLStreamReader(new ByteArrayInputStream(soapXml.getBytes()));
        String stockName = null;
        boolean inBody = false;
        boolean inGetStockPrice = false;
        boolean inStockName = false;
        while (reader.hasNext()) {
            int event = reader.next();
            switch (event) {
                case XMLStreamConstants.START_ELEMENT:
                    String localName = reader.getLocalName();
                    String nsUri = reader.getNamespaceURI();
                    if ("Body".equals(localName) && "http://schemas.xmlsoap.org/soap/envelope/".equals(nsUri)) {
                        inBody = true;
                    } else if (inBody && "GetStockPrice".equals(localName)) {
                        inGetStockPrice = true;
                    } else if (inGetStockPrice && "StockName".equals(localName) && "http://www.example.org/stock".equals(nsUri)) {
                        inStockName = true;
                    }
                    break;
                case XMLStreamConstants.CHARACTERS:
                    if (inStockName) {
                        stockName = reader.getText().trim();
                    }
                    break;
                case XMLStreamConstants.END_ELEMENT:
                    localName = reader.getLocalName();
                    if ("Body".equals(localName)) {
                        inBody = false;
                    } else if (inGetStockPrice && "GetStockPrice".equals(localName)) {
                        inGetStockPrice = false;
                    } else if (inStockName && "StockName".equals(localName)) {
                        inStockName = false;
                    }
                    break;
            }
        }
        reader.close();
        System.out.println("Stock Name (from StAX): " + stockName);
    }
    public static void main(String[] args) throws XMLStreamException {
        String soapXml = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
                "  <soap:Body>\n" +
                "    <m:GetStockPrice xmlns:m=\"http://www.example.org/stock\">\n" +
                "      <m:StockName>IBM</m:StockName>\n" +
                "    </m:GetStockPrice>\n" +
                "  </soap:Body>\n" +
                "</soap:Envelope>";
        parseWithStax(soapXml);
    }
}

总结与选择建议

方法 优点 缺点 适用场景
DOM (JAXP) 编程模型简单直观,易于操作。
整个文档在内存中,方便随机访问。
内存消耗大,不适合处理大文件。
解析速度相对较慢。
解析小规模、结构固定的 SOAP 响应,快速获取少量数据。
SAX (JAXP) 内存占用极小,性能高,适合处理超大文件。
读取速度快。
编程模型复杂,是事件驱动,不易维护。
只能顺序读取,无法随机访问。
解析超大的 SOAP 消息,或者对内存有严格限制的环境。
StAX (e.g., Woodstox) 性能高,介于 SAX 和 DOM 之间。
Pull 模式,代码比 SAX 更清晰可控。
内存占用低。
比 DOM 复杂,需要维护状态。 需要高性能解析,且不希望像 SAX 那样编程繁琐的场景,是现代 Java XML 解析的推荐选择之一。
SOAP 框架 (Axis2/CXF) 最简单、最安全,完全屏蔽了 XML 细节。
提供类型安全的客户端调用。
支持 WS-* 标准(安全、事务等)。
引入框架依赖,项目体积增大。
需要 WSDL 文件,灵活性稍差。
强烈推荐,当有 WSDL 文件时,这是与 Web Service 交互的标准做法
WSDL4J 专门用于解析 WSDL,API 清晰。 功能单一,不能直接解析 SOAP 消息体。 需要动态分析 WSDL 文档内容,获取服务元数据时使用。

最终建议

  1. 首选方案:如果你有 WSDL 文件,请务必使用 Apache Axis2Apache CXF 生成客户端代码,这是最健壮、最省力、最不容易出错的方式。
  2. 次选方案:如果你没有 WSDL,或者需要手动处理一些 SOAP 消息,并且追求性能,请使用 StAX(如 Woodstox)。
  3. 备用方案:XML 文件非常小,且解析逻辑简单,为了不引入额外依赖,可以使用 DOM
  4. 尽量避免:除非有特殊原因(如内存限制且文件超大),否则尽量避免直接使用 SAX,因为它的编程模型在复杂场景下难以维护。
分享:
扫描分享到社交APP
上一篇
下一篇