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

Spring Boot WebClient:基于响应体将200状态判定为错误的处理

解决Spring WebClient声明式客户端的非常规API错误处理问题

核心问题拆解

你遇到的两个关键痛点:

  • HTTP响应体是一次性订阅的Flux,在defaultStatusHandler中读取后,后续调用者无法再获取内容
  • 第三方API的错误逻辑不符合REST规范:401(密钥不存在)需抛出中断,200但successful=false(无资源权限)也需抛出异常

最优解决方案:全局ExchangeFilterFunction(支持多项目复用)

自定义全局过滤器统一处理所有请求的响应,既解决响应体重复读取问题,又能复用错误逻辑,适合多项目场景。

步骤1:定义统一响应体DTO

先定义匹配第三方API响应的通用DTO(示例为ApiResponse<T>):

public class ApiResponse<T> {
    private boolean successful;
    private T data;
    private String errorMessage;

    // getter、setter、构造器省略
}

步骤2:实现全局ExchangeFilterFunction

通过exchangeToMono缓存响应体,避免重复读取问题,同时处理两种错误场景:

import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.core.publisher.Mono;

public class ApiErrorHandlingFilter implements ExchangeFilterFunction {

    @Override
    public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
        return next.exchange(request)
                .flatMap(clientResponse -> {
                    // 缓存响应体,支持多次订阅
                    return clientResponse.bodyToMono(ApiResponse.class)
                            .cache()
                            .flatMap(apiResponse -> {
                                // 处理401状态码:抛出未授权异常
                                if (clientResponse.statusCode().value() == 401) {
                                    return Mono.error(new UnauthorizedException("API密钥不存在或无效"));
                                }
                                // 处理200但successful=false的情况:抛出权限不足异常
                                if (clientResponse.statusCode().is2xxSuccessful() && !apiResponse.isSuccessful()) {
                                    return Mono.error(new AccessDeniedException(apiResponse.getErrorMessage()));
                                }
                                // 正常情况:重新包装ClientResponse,传递缓存的响应体
                                return Mono.just(clientResponse.mutate()
                                        .body(Mono.just(apiResponse))
                                        .build());
                            });
                });
    }
}

步骤3:配置WebClient并绑定声明式客户端

将过滤器添加到WebClient.Builder,声明式客户端使用该Builder:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient apiWebClient() {
        return WebClient.builder()
                .baseUrl("第三方API基础地址")
                .filter(new ApiErrorHandlingFilter())
                .build();
    }

    @Bean
    public YourDeclarativeApiClient declarativeApiClient(WebClient apiWebClient) {
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(apiWebClient)).build();
        return factory.createClient(YourDeclarativeApiClient.class);
    }
}

步骤4:声明式客户端接口示例

import org.springframework.web.service.annotation.GetExchange;
import reactor.core.publisher.Mono;

public interface YourDeclarativeApiClient {

    @GetExchange("/resource/{id}")
    Mono<ApiResponse<ResourceDTO>> getResource(@PathVariable("id") String id);
}

关键细节说明

  • 响应体缓存:使用.cache()操作符缓存响应体Mono,确保后续订阅(比如业务代码读取响应)能拿到数据
  • 全局复用:将ApiErrorHandlingFilter和WebClientConfig封装到公共组件Jar中,多项目直接引入即可复用错误逻辑
  • 异常类型:自定义UnauthorizedException和AccessDeniedException,方便业务层统一捕获处理

替代方案:接口级别局部处理(适合特殊接口)

如果仅需针对个别接口处理,可在声明式方法中直接处理响应:

@GetExchange("/resource/{id}")
default Mono<ResourceDTO> getResourceWithErrorHandling(@PathVariable("id") String id) {
    return getResource(id)
            .flatMap(response -> {
                if (!response.isSuccessful()) {
                    return Mono.error(new AccessDeniedException(response.getErrorMessage()));
                }
                return Mono.just(response.getData());
            });
}

内容的提问来源于stack exchange,提问作者Nándor Holozsnyák

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 11:37:50