Java Spring Boot如何读取外层包裹payload节点的SOAP XML请求
Spring Boot解析外层包裹payload节点的SOAP请求方案
错误根因
Spring WS默认的SOAP解析逻辑要求请求的根节点必须是http://schemas.xmlsoap.org/soap/envelope/命名空间下的Envelope节点,你当前的请求根节点是自定义的payload,所以触发了Unable to create envelope from given source because the root element is not named "Envelope"的报错。
可选解决方案
方案1:自定义请求拦截器提前剥离payload节点(侵入性最低,推荐)
不需要修改默认SOAP解析核心逻辑,在请求进入解析流程前先剥离外层payload节点即可,下面是服务端接收请求的示例实现:
- 实现端点拦截器,完成payload节点提取替换
import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.EndpointInterceptor; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; public class PayloadStrippingInterceptor implements EndpointInterceptor { // XML安全配置,避免XXE注入 private static final DocumentBuilderFactory DOC_BUILDER_FACTORY; static { DOC_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); try { DOC_BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DOC_BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false); DOC_BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false); DOC_BUILDER_FACTORY.setNamespaceAware(true); } catch (Exception e) { throw new RuntimeException("XML解析器初始化失败", e); } } @Override public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { SaajSoapMessage originalRequest = (SaajSoapMessage) messageContext.getRequest(); // 读取原始请求内容 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); TransformerFactory.newInstance().newTransformer().transform(originalRequest.getPayloadSource(), new StreamResult(outputStream)); byte[] rawContent = outputStream.toByteArray(); // 解析XML提取Envelope节点 Document doc = DOC_BUILDER_FACTORY.newDocumentBuilder().parse(new ByteArrayInputStream(rawContent)); Element rootPayload = doc.getDocumentElement(); Node envelopeNode = rootPayload.getElementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/", "Envelope").item(0); if (envelopeNode == null) { // 没有找到Envelope节点,直接放行走原有报错逻辑即可 return true; } // 替换根节点为Envelope doc.removeChild(rootPayload); doc.appendChild(doc.importNode(envelopeNode, true)); // 重新构造请求替换原有请求 messageContext.setRequest(new SaajSoapMessage(new DOMSource(doc))); return true; } @Override public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { return true; } @Override public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { return true; } @Override public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception { } }
- 注册拦截器到WS配置中
import org.springframework.context.annotation.Configuration; import org.springframework.ws.config.annotation.EnableWs; import org.springframework.ws.config.annotation.WsConfigurerAdapter; import org.springframework.ws.server.EndpointInterceptor; import java.util.List; @EnableWs @Configuration public class WebServiceConfig extends WsConfigurerAdapter { @Override public void addInterceptors(List<EndpointInterceptor> interceptors) { // 自定义拦截器放在最前面,优先处理请求 interceptors.add(0, new PayloadStrippingInterceptor()); } }
方案2:自定义SOAP消息工厂
如果是客户端发送请求或者需要全链路统一处理这类格式,可以重写SaajSoapMessageFactory的createWebServiceMessage方法,在消息实例化阶段就完成payload节点的剥离,适配更复杂的自定义包裹场景。
提示:如果需要兼容外层带payload和不带payload的两种请求格式,可以在拦截器中增加根节点判断逻辑,只有根节点是
payload时才执行剥离操作,不影响正常SOAP请求的处理。
内容的提问来源于stack exchange,提问作者ashishh87
相关产品推荐
相关产品推荐

