在Axis2中获取SOAPBody字符串时遇NodeUnavailableException求助
问题场景
在使用Axis2和Tomcat9开发Web服务时,尝试通过以下代码获取SOAPBody的字符串形式:
public static String getXMLSoapBody() { log.getLogger().debug("TEST - getXmlSoapBody - START"); String soapBody; SOAPBody body; MessageContext messageContext = MessageContext.getCurrentMessageContext(); try { SOAPEnvelope env = messageContext.getEnvelope(); body = env.getBody(); } catch(Exception e) { log.getLogger().error("SOAP Exception :" + e.toString()); e.printStackTrace(); return null; } soapBody = body.toString(); return soapBody; }
执行后抛出NodeUnavailableException,完整栈信息如下:
org.apache.axiom.om.NodeUnavailableException at org.apache.axiom.om.impl.common.AxiomExceptionTranslator.translate(AxiomExceptionTranslator.java:35) at org.apache.axiom.om.impl.llom.AxiomContainerImpl.serialize(Unknown Source) at org.apache.axiom.om.impl.llom.AxiomContainerImpl.serializeAndSurfaceIOException(Unknown Source) at org.apache.axiom.om.impl.llom.AxiomContainerImpl.serialize(Unknown Source) at org.apache.axiom.om.impl.llom.AxiomContainerImpl.serialize(Unknown Source) at org.apache.axiom.om.impl.llom.AxiomContainerImpl.serialize(Unknown Source) at org.apache.axiom.om.impl.llom.AxiomElementImpl.toString(Unknown Source) at uk.co.celesio.orders.server.TransUtils.getXMLSoapBody(TransUtils.java:304)
同时伴随Tomcat日志报错:
10:41:26.935 [http-nio-8080-exec-2] ERROR org.apache.axis2.transport.http.AxisServlet - processAxisFault() found a null HTTP status from the MessageContext instance, setting HttpServletResponse status to: axis2.http.response.state
测试请求示例:
<?xml version='1.0' encoding='windows-1252'?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soapenv:Body> <MyTestRequest xmlns="..."> ... </MyTestRequest> </soapenv:Body> </soapenv:Envelope>
原因分析
NodeUnavailableException本质是Axis2默认采用的Axiom流式XML处理模型导致的:
- Axis2为优化性能,默认以流方式解析SOAP消息,解析完成后底层XML输入流会被关闭或释放
- 当业务代码中延迟调用
body.toString()时,SOAPBody对应的OM节点已无法访问底层流数据,触发该异常
解决方案
方案1:使用Axiom序列化API直接生成字符串
避免直接调用toString(),改用Axiom提供的序列化工具类,确保在节点可用时完成序列化:
import org.apache.axiom.om.OMElement; import org.apache.axis2.context.MessageContext; import javax.xml.stream.XMLStreamException; import java.io.ByteArrayOutputStream; import java.io.IOException; public static String getXMLSoapBody() { log.getLogger().debug("TEST - getXmlSoapBody - START"); MessageContext messageContext = MessageContext.getCurrentMessageContext(); try { SOAPBody body = messageContext.getEnvelope().getBody(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 使用Axiom序列化API输出SOAPBody内容 body.serialize(baos); return baos.toString(); } catch (XMLStreamException | IOException e) { log.getLogger().error("获取SOAPBody失败: " + e.getMessage(), e); return null; } }
方案2:提前缓存SOAP消息(推荐大消息场景)
在消息处理的早期阶段(如In拦截器)缓存完整SOAP消息,后续直接从缓存读取,避免重复访问流数据:
- 自定义缓存拦截器:
import org.apache.axis2.AxisFault; import org.apache.axis2.context.MessageContext; import org.apache.axis2.handlers.AbstractHandler; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class SoapBodyCacheInterceptor extends AbstractHandler { @Override public InvocationResponse invoke(MessageContext msgContext) throws AxisFault { try { InputStream in = msgContext.getEnvelope().getXMLStreamReader().getInputSource().getByteStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) != -1) { baos.write(buffer, 0, len); } // 将缓存的SOAP消息存入MessageContext msgContext.setProperty("CACHED_SOAP_BODY", baos.toString()); // 重置输入流,确保后续处理能正常读取 msgContext.getEnvelope().getXMLStreamReader().getInputSource().setByteStream(new ByteArrayInputStream(baos.toByteArray())); } catch (IOException e) { throw new AxisFault("缓存SOAP消息失败", e); } return InvocationResponse.CONTINUE; } }
- 在
axis2.xml中配置拦截器(放入PreDispatch阶段):
<phase name="PreDispatch" phaseOrder="First"> <handler name="SoapBodyCacheInterceptor" class="your.package.SoapBodyCacheInterceptor"/> </phase>
- 业务代码中直接读取缓存:
public static String getXMLSoapBody() { log.getLogger().debug("TEST - getXmlSoapBody - START"); MessageContext messageContext = MessageContext.getCurrentMessageContext(); return (String) messageContext.getProperty("CACHED_SOAP_BODY"); }
方案3:修改Axis2消息处理模式(不推荐大消息场景)
通过配置强制Axiom使用缓存模式,关闭流式处理:
在axis2.xml中添加参数:
<parameter name="enableOMBuilderCache">true</parameter>
该方式会增加内存消耗,仅适合小消息场景。
内容的提问来源于stack exchange,提问作者Bohdan Hrytsai
相关产品推荐
相关产品推荐

