Spring Boot WebClient记录不可解析响应的首选机制是什么?
解决WebClient解析失败时无法获取原始响应体的方案
下面是几种可行的方案,无需完全依赖手动转字符串再反序列化的方式:
方案1:通过exchangeToMono缓存响应体
利用exchangeToMono获取完整的ClientResponse,先将响应体缓存为DataBuffer,再尝试解析。解析失败时,从缓存中读取原始响应体内容:
webClient.get() .uri("/api/endpoint") .exchangeToMono(clientResponse -> { // 缓存响应体,避免被消费后无法重复读取 return clientResponse.bodyToMono(DataBuffer.class) .flatMap(dataBuffer -> { // 复制DataBuffer,防止后续操作导致释放 DataBuffer cachedBuffer = dataBufferFactory.wrap(dataBuffer.asByteBuffer()); // 尝试解析目标对象 ObjectMapper mapper = new ObjectMapper(); Mono<YourTargetClass> targetMono = Mono.just(cachedBuffer) .map(buffer -> { try { return mapper.readValue(buffer.asInputStream(), YourTargetClass.class); } catch (IOException e) { // 解析失败,读取原始响应体 String rawBody = StandardCharsets.UTF_8.decode(cachedBuffer.asByteBuffer()).toString(); // 记录日志 log.error("解析响应失败,原始响应体: {}", rawBody); // 抛出包含原始内容的自定义异常,上层可以捕获并返回给客户端 throw new ResponseParseException("响应解析失败", rawBody, e); } finally { // 释放DataBuffer,避免内存泄漏 DataBufferUtils.release(cachedBuffer); DataBufferUtils.release(dataBuffer); } }); return targetMono; }); }) .onErrorResume(ResponseParseException.class, e -> { // 处理自定义异常,返回原始响应体给客户端 return Mono.just(ResponseEntity.badRequest().body(e.getRawBody())); });
注意:必须正确释放DataBuffer,否则会造成内存泄漏;如果响应是分块传输的,可使用DataBufferUtils.join()合并所有分块后再处理。
方案2:自定义Jackson解码器捕获解析异常
继承Jackson2JsonDecoder,重写decode方法,在解析失败时捕获异常并提取原始响应体:
public class LoggableJackson2JsonDecoder extends Jackson2JsonDecoder { private static final Logger log = LoggerFactory.getLogger(LoggableJackson2JsonDecoder.class); public LoggableJackson2JsonDecoder(ObjectMapper mapper) { super(mapper); } @Override public Mono<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType, MimeType mimeType, Map<String, Object> hints) { // 收集所有DataBuffer,转为字符串 return DataBufferUtils.join(inputStream) .flatMap(dataBuffer -> { String rawBody = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer()).toString(); DataBufferUtils.release(dataBuffer); // 重新包装为DataBuffer流,供父类解析 Publisher<DataBuffer> bufferedInput = Mono.just(dataBufferFactory.wrap(rawBody.getBytes(StandardCharsets.UTF_8))); return super.decode(bufferedInput, elementType, mimeType, hints) .onErrorMap(Throwable.class, e -> { log.error("解析响应失败,原始响应体: {}", rawBody); return new ResponseParseException("响应解析失败", rawBody, e); }); }); } }
然后在配置WebClient时使用自定义解码器:
WebClient webClient = WebClient.builder() .codecs(configurer -> { configurer.defaultCodecs().jackson2JsonDecoder(new LoggableJackson2JsonDecoder(new ObjectMapper())); }) .build();
这样使用bodyToMono(YourTargetClass.class)时,一旦解析失败,会自动记录原始响应体并抛出带原始内容的异常,上层可以捕获处理。
方案3:优化手动字符串反序列化的封装
如果你倾向于先转字符串,可以封装通用方法减少重复代码:
private <T> Mono<T> decodeRawBody(Mono<String> rawBodyMono, Class<T> targetClass) { ObjectMapper mapper = new ObjectMapper(); return rawBodyMono.flatMap(rawBody -> { try { T result = mapper.readValue(rawBody, targetClass); return Mono.just(result); } catch (IOException e) { log.error("解析响应失败,原始响应体: {}", rawBody); return Mono.error(new ResponseParseException("响应解析失败", rawBody, e)); } }); }
使用时:
webClient.get() .uri("/api/endpoint") .retrieve() .bodyToMono(String.class) .flatMap(rawBody -> decodeRawBody(Mono.just(rawBody), YourTargetClass.class)) .onErrorResume(ResponseParseException.class, e -> { return Mono.just(ResponseEntity.badRequest().body(e.getRawBody())); });
这种方式简单直接,适合快速改造现有代码。
内容的提问来源于stack exchange,提问作者Core
相关产品推荐
相关产品推荐

