使用ContainerResponseFilter获取响应流为空,如何结合WriterInterceptor打日志?
问题:无法通过ContainerResponseContext获取响应实体流
我尝试用ContainerResponseContext的getEntityStream()捕获响应实体流,代码如下:
public String getResponseEntityStream(ContainerResponseContext context) throws IOException { OutputStream originalStream = context.getEntityStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); context.setEntityStream(baos); String body = baos.toString(); baos.writeTo(originalStream); baos.close(); context.setEntityStream(originalStream); return body; }
我在ContainerResponseFilter的实现中调用上述方法获取完整响应流:
@Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { StringBuilder sbResponse = new StringBuilder(); sbResponse.append(" - Response Entity Class: ").append(responseContext.getEntityClass()) .append(System.getProperty("line.separator")); sbResponse.append(" - Response Entity Stream: ").append(getResponseEntityStream(responseContext)) .append(System.getProperty("line.separator")); System.out.println("HTTP RESPONSE : " + "\n" + sbResponse.toString()); }
核心问题
上述代码中,Response Entity Stream始终为空。我想在处理流之前读取完整的响应实体流并打印原始内容,请问问题出在哪?
补充说明
我可以用WriterInterceptor获取响应体,但它只在有响应payload时才会被调用。我用ContainerResponseFilter是因为想在无payload时也能读取响应详情,而且WriterInterceptor没法像ContainerResponseFilter那样获取getStatus()和getStatusInfo()。有没有办法通过Thread Local之类的方式结合两者的数据,把完整响应打印到日志里?
解答
为什么当前代码获取不到流?
你的逻辑完全搞反了:
- 在
ContainerResponseFilter阶段,JAX-RS还没把响应实体写入到流里,你替换成ByteArrayOutputStream后直接读,自然是空的。 - 替换流后没有触发实体的写入操作,就直接把原流换回去,等于啥都没做。
正确的处理方式
要在ContainerResponseFilter中读取响应体,需要先让JAX-RS把实体写入到缓存流,再读取缓存的内容,最后把流恢复回去供后续使用:
public String getResponseEntityStream(ContainerResponseContext context) throws IOException { // 先获取原流,并用缓存流包装 OutputStream originalStream = context.getEntityStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); context.setEntityStream(baos); // 关键:触发实体写入操作,用MessageBodyWriter把实体序列化到缓存流 Object entity = context.getEntity(); if (entity != null) { Class<?> entityClass = context.getEntityClass(); Type entityType = context.getEntityType(); Annotation[] annotations = context.getEntityAnnotations(); MediaType mediaType = context.getMediaType(); MultivaluedMap<String, Object> headers = context.getHeaders(); // 获取对应的MessageBodyWriter MessageBodyWriter<Object> writer = context.getProviders().getMessageBodyWriter((Class<Object>) entityClass, entityType, annotations, mediaType); if (writer != null) { writer.writeTo(entity, entityClass, entityType, annotations, mediaType, headers, baos); } } // 读取缓存的内容 String body = baos.toString(StandardCharsets.UTF_8.name()); // 把缓存的内容写回原流,保证后续流程能拿到数据 baos.writeTo(originalStream); // 恢复原流 context.setEntityStream(originalStream); baos.close(); return body; }
结合ContainerResponseFilter和WriterInterceptor的方案
如果觉得上面的方式太繁琐,用Thread Local结合两者是更优雅的选择:
- 定义一个Thread Local存储响应元数据(状态码、状态信息等):
public class ResponseMetadataHolder { private static final ThreadLocal<ResponseMetadata> THREAD_LOCAL = new ThreadLocal<>(); public static void set(ResponseMetadata metadata) { THREAD_LOCAL.set(metadata); } public static ResponseMetadata get() { return THREAD_LOCAL.get(); } public static void clear() { THREAD_LOCAL.remove(); } public static class ResponseMetadata { private int status; private Response.StatusType statusInfo; // getter和setter public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public Response.StatusType getStatusInfo() { return statusInfo; } public void setStatusInfo(Response.StatusType statusInfo) { this.statusInfo = statusInfo; } } }
- 在
ContainerResponseFilter中存入响应元数据:
@Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { ResponseMetadataHolder.ResponseMetadata metadata = new ResponseMetadataHolder.ResponseMetadata(); metadata.setStatus(responseContext.getStatus()); metadata.setStatusInfo(responseContext.getStatusInfo()); ResponseMetadataHolder.set(metadata); // 处理无payload的情况,直接打印元数据 if (responseContext.getEntity() == null) { StringBuilder sb = new StringBuilder(); sb.append("HTTP RESPONSE:\n") .append(" - Status: ").append(metadata.getStatus()) .append("\n - Status Info: ").append(metadata.getStatusInfo()) .append("\n"); System.out.println(sb); ResponseMetadataHolder.clear(); } }
- 在
WriterInterceptor中获取元数据和响应体,组合后打印:
@Provider public class ResponseBodyInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { // 用缓存流包装原流 OutputStream originalStream = context.getOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); context.setOutputStream(baos); // 执行写入操作 context.proceed(); // 读取响应体 String body = baos.toString(StandardCharsets.UTF_8.name()); // 写回原流 baos.writeTo(originalStream); // 获取Thread Local中的元数据 ResponseMetadataHolder.ResponseMetadata metadata = ResponseMetadataHolder.get(); if (metadata != null) { StringBuilder sb = new StringBuilder(); sb.append("HTTP RESPONSE:\n") .append(" - Status: ").append(metadata.getStatus()) .append("\n - Status Info: ").append(metadata.getStatusInfo()) .append("\n - Response Body: ").append(body) .append("\n"); System.out.println(sb); } // 清理Thread Local,防止内存泄漏 ResponseMetadataHolder.clear(); // 恢复原流 context.setOutputStream(originalStream); baos.close(); } }
注意:一定要记得在处理完成后清理Thread Local,避免内存泄漏。
内容的提问来源于stack exchange,提问作者jane
相关产品推荐
相关产品推荐

