Spring Boot中ResponseBodyAdvice的beforeBodyWrite无法获取响应状态与头信息
问题原因及解决方案
为什么原代码无法获取响应状态码和头?
ResponseBodyAdvice.beforeBodyWrite方法的执行时机是HTTP消息转换器写入响应体之前,此时Spring MVC尚未将最终的响应状态码、响应头写入到ServletResponse中(这些元数据通常在后续请求处理阶段,比如异常处理、响应提交前才会被设置),所以你强转后获取到的是ServletResponse的初始状态,自然无法拿到实际的响应信息。
解决方案
方案1:使用HandlerInterceptor的afterCompletion方法
该方法在整个请求处理完成(响应已完全准备好,即将发送给客户端)后执行,此时可以可靠获取所有响应元数据。
第一步:实现HandlerInterceptor
@Component @Slf4j public class ResponseLogInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 获取响应状态码 int statusCode = response.getStatus(); // 整理响应头为Map格式,方便日志输出 Map<String, String> responseHeaders = new HashMap<>(); response.getHeaderNames().forEach(headerName -> responseHeaders.put(headerName, response.getHeader(headerName)) ); log.info("response code is {}", statusCode); log.info("response headers are {}", responseHeaders); } }
第二步:注册拦截器
@Configuration public class WebMvcConfig implements WebMvcConfigurer { private final ResponseLogInterceptor responseLogInterceptor; public WebMvcConfig(ResponseLogInterceptor responseLogInterceptor) { this.responseLogInterceptor = responseLogInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(responseLogInterceptor) .addPathPatterns("/**"); // 匹配所有需要日志的请求路径 } }
方案2:同时记录响应体、状态码和头(推荐)
如果需要同时记录响应体,可结合ContentCachingResponseWrapper包装响应,缓存响应内容后在afterCompletion中统一获取所有信息:
@Component @Slf4j public class CompleteResponseLogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 用ContentCachingResponseWrapper包装原始响应,缓存响应内容 if (!(response instanceof ContentCachingResponseWrapper)) { response = new ContentCachingResponseWrapper(response); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ContentCachingResponseWrapper cachedResponse = (ContentCachingResponseWrapper) response; // 获取响应状态码 int statusCode = cachedResponse.getStatus(); // 整理响应头 Map<String, String> responseHeaders = new HashMap<>(); cachedResponse.getHeaderNames().forEach(headerName -> responseHeaders.put(headerName, cachedResponse.getHeader(headerName)) ); // 获取响应体(注意编码格式,避免乱码) String responseBody = new String(cachedResponse.getContentAsByteArray(), cachedResponse.getCharacterEncoding()); log.info("response code is {}", statusCode); log.info("response headers are {}", responseHeaders); log.info("response body is {}", responseBody); // 必须将缓存的响应内容写出到原始响应,否则客户端无法收到响应 cachedResponse.copyBodyToResponse(); } }
同样需要在WebMvcConfig中注册这个拦截器。
内容的提问来源于stack exchange,提问作者Albin
相关产品推荐
相关产品推荐

