Spring Boot开发SOAP-REST中转服务及SOAP转对象报错求助
SOAP转REST中间Wrapper服务问题排查
需求背景
开发一个中间Wrapper服务,流程为:接收SOAP请求消息并转换为JSON,调用REST API,将返回的JSON响应转换为SOAP响应后返回给调用方,实现SOAP与REST通信的中转。已有对应的WSDL,基于Spring Boot开发,目前在SOAP消息映射到生成类时出错。
当前实现代码
@RestController public class WrapperController { @PostMapping( value = "/wrap", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public Q248Req wrapSOAPRequest(@RequestBody String SOAPReq) throws SOAPException, IOException, JAXBException { System.out.println("SOAP Request: " + SOAPReq); SOAPMessage message = MessageFactory.newInstance() .createMessage(null, new ByteArrayInputStream(SOAPReq.getBytes())); Unmarshaller unmarshaller = JAXBContext.newInstance(TestObj.class).createUnmarshaller(); TestObj Req = (TestObj) unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument()); return Req; } }
错误信息
SOAP Body: [#document: null] 2022-11-14 12:36:43.888 ERROR 21596 --- [nio-9080-exec-3] c.s.x.m.s.s.impl : SAAJ0251: 无法从Body中提取Document 2022-11-14 12:36:43.896 ERROR 21596 --- [nio-9080-exec-3] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/wrapper-service/v1/decisions/q248] threw exception [Request processing failed; nested exception is com.sun.xml.messaging.saaj.SOAPExceptionImpl: Unable to extract Document from body] with root cause java.lang.ClassCastException: class com.sun.org.apache.xerces.internal.dom.TextImpl cannot be cast to class org.w3c.dom.Element (com.sun.org.apache.xerces.internal.dom.TextImpl and org.w3c.dom.Element are in module java.xml of loader 'bootstrap') at com.sun.xml.messaging.saaj.soap.impl.BodyImpl.extractContentAsDocument(BodyImpl.java:338) ~[saaj-impl-1.5.3.jar:1.5.3] at com.lbg.rsk.cdp.core.controller.WrapperController.wrapSOAPRequest(WrapperController.java:265) ~[main/:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.18.jar:5.3.18] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.18.jar:5.3.18] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.18.jar:5.3.18] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.18.jar:5.3.18] at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.60.jar:4.0.FR] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.18.jar:5.3.18] at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.60.jar:4.0.FR] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.18.jar:5.3.18] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.18.jar:5.3.18] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[spring-boot-actuator-2.6.6.jar:2.6.6] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.18.jar:5.3.18] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-embed-core-9.0.60.jar:9.0.60] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.60.jar:9.0.60] at java.lang.Thread.run(Thread.java:829) [?:?]
问题分析与解决方案
问题根源
extractContentAsDocument()方法尝试将SOAP Body内容转为Document时,出现了TextImpl转Element的类型转换错误,说明SOAP Body的结构不符合预期——可能Body中包含空白文本节点,或者请求的SOAP消息格式不正确,导致解析时无法正确提取到Element节点。
修复方案
直接解析SOAP Body的子元素
跳过extractContentAsDocument(),直接遍历SOAP Body的子节点,筛选出实际的请求Element节点再进行JAXB解析:@RestController public class WrapperController { @PostMapping( value = "/wrap", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public Q248Req wrapSOAPRequest(@RequestBody String SOAPReq) throws SOAPException, IOException, JAXBException { System.out.println("SOAP Request: " + SOAPReq); SOAPMessage message = MessageFactory.newInstance() .createMessage(null, new ByteArrayInputStream(SOAPReq.getBytes(StandardCharsets.UTF_8))); // 遍历Body子节点,找到实际的请求元素 NodeList nodeList = message.getSOAPBody().getChildNodes(); Element requestElement = null; for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { requestElement = (Element) node; break; } } if (requestElement == null) { throw new IllegalArgumentException("SOAP Body中未找到有效请求元素"); } Unmarshaller unmarshaller = JAXBContext.newInstance(TestObj.class).createUnmarshaller(); TestObj req = (TestObj) unmarshaller.unmarshal(requestElement); return req; } }校验SOAP请求格式
确保传入的SOAP请求是标准格式,Body中仅包含目标请求元素,无多余空白或格式错误。标准SOAP请求结构示例:<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://yournamespace.com/test"> <soapenv:Header/> <soapenv:Body> <test:YourRequestElement> <!-- 请求内容 --> </test:YourRequestElement> </soapenv:Body> </soapenv:Envelope>使用Spring WS简化SOAP处理
引入Spring WS框架自动绑定SOAP消息到JAXB生成类,避免手动解析SOAPMessage的繁琐:- 添加Spring WS依赖到项目配置文件
- 创建Endpoint类直接接收解析后的对象:
@Endpoint public class SoapEndpoint { private static final String NAMESPACE_URI = "http://yournamespace.com/test"; @PayloadRoot(namespace = NAMESPACE_URI, localPart = "YourRequestElement") @ResponsePayload public Q248Req handleRequest(@RequestPayload TestObj request) { // 直接使用解析后的TestObj调用REST API return request; } }
内容的提问来源于stack exchange,提问作者Somnath Mukherjee
相关产品推荐
相关产品推荐

