使用Interceptor记录REST服务,无法获取请求与响应体
解决CXF拦截器无法获取JSON请求/响应体的问题
你已经搭好了拦截器的基础框架,但因为拦截器阶段选择和流处理的细节问题,没法拿到请求/响应体。下面我会一步步告诉你怎么调整代码来实现需求:
一、获取JSON请求体(入站拦截器)
你当前的入站拦截器用的是Phase.PRE_INVOKE阶段,这个阶段CXF已经把请求流解析成Java对象了,原始的请求流已经被消耗掉,自然拿不到内容。我们需要把拦截器阶段调整到Phase.POST_STREAM——这个阶段请求流已经被接收,但还没被CXF解析,正好适合读取请求体。
修改ContextJSONInInterceptor的代码:
import org.apache.cxf.helpers.IOUtils; import org.apache.cxf.io.CachedInputStream; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; public class ContextJSONInInterceptor extends AbstractPhaseInterceptor<Message> { public ContextJSONInInterceptor() { // 调整到POST_STREAM阶段 super(Phase.POST_STREAM); } @Override public void handleMessage(Message message) throws Fault { InputStream is = message.getContent(InputStream.class); if (is != null) { try { // 确保流是可重置的,如果不是就用CachedInputStream包装 if (!is.markSupported()) { is = new CachedInputStream(is); message.setContent(InputStream.class, is); } // 读取请求体内容 String requestBody = IOUtils.toString(is, StandardCharsets.UTF_8); System.out.println("Request Body: " + requestBody); // 重置流,让后续CXF的解析逻辑能正常读取 is.reset(); } catch (IOException e) { throw new Fault(new RuntimeException("Failed to read request body", e)); } } // 修正原代码的错误:要从message中获取实际的Content-Type,而不是直接打印常量 System.out.println("Content-Type: " + message.get(Message.CONTENT_TYPE)); System.out.println("---------------"); } }
二、获取JSON响应体(出站拦截器)
出站的情况更特殊:响应流是只写的,直接读取会失败。我们需要用CXF提供的CachedOutputStream来缓存响应内容,等响应写完后再读取缓存的内容。同时,拦截器阶段要调整到Phase.PRE_STREAM,确保在响应写入流之前就完成包装。
修改ContextJSONOutInterceptor的代码:
import org.apache.cxf.helpers.IOUtils; import org.apache.cxf.io.CachedOutputStream; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; public class ContextJSONOutInterceptor extends AbstractPhaseInterceptor<Message> { public ContextJSONOutInterceptor() { // 调整到PRE_STREAM阶段 super(Phase.PRE_STREAM); } @Override public void handleMessage(Message message) throws Fault { OutputStream os = message.getContent(OutputStream.class); if (os != null) { // 用CachedOutputStream包装原输出流,缓存所有写入的内容 CachedOutputStream cos = new CachedOutputStream(os); message.setContent(OutputStream.class, cos); // 添加一个在SEND_ENDING阶段执行的拦截器,此时响应内容已经全部写入缓存 message.getInterceptorChain().add(new AbstractPhaseInterceptor<Message>(Phase.SEND_ENDING) { @Override public void handleMessage(Message message) throws Fault { try { // 读取缓存的响应体内容 String responseBody = IOUtils.toString(cos.getInputStream(), StandardCharsets.UTF_8); System.out.println("Response Body: " + responseBody); // 将缓存的内容写回原输出流,确保客户端能收到响应 cos.writeCacheTo(os); // 关闭缓存流 cos.close(); } catch (IOException e) { throw new Fault(new RuntimeException("Failed to read response body", e)); } } }); } // 修正原代码的错误:获取实际的Content-Type System.out.println("Content-Type: " + message.get(Message.CONTENT_TYPE)); System.out.println("---------------"); } }
三、关键注意事项
- 拦截器阶段选择:入站用
POST_STREAM、出站用PRE_STREAM是核心,选错阶段要么流已经被消耗,要么内容还没生成。 - 流的重置/缓存:读取请求流后必须重置,否则后续CXF解析会报错;响应流必须用
CachedOutputStream包装,才能实现“先写后读”。 - 依赖确保:确保你的项目中包含
cxf-core包(提供CachedInputStream/CachedOutputStream)和commons-io包(提供IOUtils,也可以自己实现流读取逻辑)。
内容的提问来源于stack exchange,提问作者Caznik




