如何在Apache CXF中获取400/500状态码下的响应体
解决CXF客户端获取400响应中SOAP Fault内容的方案
核心问题分析
你用错了拦截器的阶段和类型:SEND阶段的OutInterceptor负责处理请求发送流程,而第三方服务的400响应属于接收响应环节,应该用InInterceptor并选择合适的接收阶段,同时要解决输入流只能读取一次的问题。
具体实现步骤
1. 自定义InInterceptor(缓存响应流+解析Fault)
创建拦截器在响应流刚被接收时缓存内容,故障发生时从缓存读取并解析SOAP Fault:
import org.apache.cxf.interceptor.Fault; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.apache.commons.io.IOUtils; import java.io.ByteArrayInputStream; import java.io.InputStream; public class SoapFaultInterceptor extends AbstractPhaseInterceptor<Message> { public SoapFaultInterceptor() { // 选择POST_STREAM阶段:响应流已接收但未被解析,适合缓存 super(Phase.POST_STREAM); } @Override public void handleMessage(Message message) throws Fault { InputStream is = message.getContent(InputStream.class); if (is == null) return; try { // 把流转换成字节数组缓存,避免流被消费后无法读取 byte[] responseBytes = IOUtils.toByteArray(is); // 将可重复读取的流放回message message.setContent(InputStream.class, new ByteArrayInputStream(responseBytes)); // 把字节数组存入message属性,供handleFault调用 message.put("CACHED_RESPONSE_BODY", responseBytes); } catch (Exception e) { throw new Fault(e); } } @Override public void handleFault(Message message) { byte[] cachedBody = (byte[]) message.get("CACHED_RESPONSE_BODY"); if (cachedBody == null) return; try { // 转换成XML字符串,解析SOAP Fault内容 String soapFaultXml = new String(cachedBody, "UTF-8"); // 这里可以用DOM/XPath/JAXB解析Fault的具体字段(示例用打印演示) System.out.println("原始SOAP Fault响应:\n" + soapFaultXml); // 可选:将解析后的Fault信息替换到异常中 Fault currentFault = (Fault) message.getContent(Exception.class); // 比如用XPath提取faultstring: // Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(cachedBody)); // String faultMsg = XPathFactory.newInstance().newXPath().evaluate("//soapenv:Fault/faultstring", doc); // currentFault.setMessage(faultMsg); } catch (Exception e) { e.printStackTrace(); } } }
2. 给CXF客户端添加拦截器
将自定义拦截器加入客户端的入站拦截器链:
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.transport.http.HTTPClientPolicy; public class WebServiceClientFactory { public static <T> T createClient(Class<T> serviceInterface, String serviceUrl) { JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setServiceClass(serviceInterface); factory.setAddress(serviceUrl); // 添加自定义拦截器 factory.getInInterceptors().add(new SoapFaultInterceptor()); // 可选配置:让CXF不直接抛出400异常,而是继续处理响应体 HTTPConduit conduit = (HTTPConduit) factory.create().getConduit(); HTTPClientPolicy policy = new HTTPClientPolicy(); policy.setHandleResponseCode(true); conduit.setClient(policy); return (T) factory.create(); } }
关键说明
- 阶段选择:
POST_STREAM是最适合缓存响应流的阶段,此时响应已被接收但未被CXF解析,不会出现流被提前消费的问题。 - 流缓存:用字节数组缓存流内容,确保故障发生时能重复读取。
- 可选配置
handleResponseCode(true):让CXF不因为400状态码直接抛出HTTPException,而是继续解析响应体中的SOAP Fault,部分场景下无需自定义拦截器即可获取Fault信息。
内容的提问来源于stack exchange,提问作者Martin Fernau
相关产品推荐
相关产品推荐

