You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 07:50:22