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

自定义HttpMessageReader配合WebClient同步读取响应遇阻塞问题

问题分析与解决方案

1. 核心异常原因:HttpMessageReader内部的阻塞操作

你的CustomHttpMessageReader在readMultipartData方法里调用了.blockFirst(),这是在Reactor非阻塞线程(reactor-http-nio-*)中执行阻塞操作,直接违反了Reactor的线程模型规则,触发了异常。哪怕你在外部调用blockOptional()同步等待,WebClient内部的消息读取逻辑依然运行在Reactor的非阻塞线程池,绝对不能在这个线程内做阻塞调用。

修复方案:重构为全Reactive实现

必须把所有阻塞操作替换为Reactive链式调用,彻底移除reader内部的block()/blockFirst()方法,参考重构后的核心代码:

public class CustomHttpMessageReader implements HttpMessageReader<CustomClass> {
    private final DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader();

    @Override
    public boolean canRead(ResolvableType elementType, MediaType mediaType) {
        return elementType.isAssignableFrom(CustomClass.class) && MediaType.MULTIPART_FORM_DATA.includes(mediaType);
    }

    @Override
    public Flux<CustomClass> read(ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints) {
        return readMultipartData(message)
                .map(this::convertPartsToCustomClass)
                .flux();
    }

    private Mono<List<CustomPart>> readMultipartData(ReactiveHttpInputMessage message) {
        return partReader.read(ResolvableType.forClass(Part.class), message, Collections.emptyMap())
                .flatMap(this::convertPartToCustomPart)
                .collectList();
    }

    private Mono<CustomPart> convertPartToCustomPart(Part part) {
        // 用Reactive方式异步读取Part内容,替代blockFirst()
        return part.content()
                .aggregate()
                .asByteArray()
                .map(content -> new CustomPart(part.name(), content, part.headers()));
    }

    private CustomClass convertPartsToCustomClass(List<CustomPart> parts) {
        // 这里实现你的Part转CustomClass逻辑
        return new CustomClass(parts);
    }
}

关键改动:

  • 全程用Mono/Flux链式调用,不中断Reactive数据流
  • 读取Part内容时用aggregate().asByteArray()异步获取,避免阻塞
  • 保持整个消息读取流程的非阻塞特性

2. 移除Netty后找不到ClientHttpConnector的问题

移除Netty依赖后,Spring Boot自动配置的Reactor Netty连接器失效,需要手动配置Servlet环境兼容的连接器(比如Tomcat)。

修复方案:添加依赖并配置Tomcat连接器

首先在build.gradle中添加Tomcat客户端依赖:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    // 添加Tomcat客户端连接器依赖
    implementation 'org.apache.tomcat.embed:tomcat-embed-core'
}

然后配置WebClient时指定Tomcat连接器:

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient webClient() {
        TomcatClientHttpConnector connector = new TomcatClientHttpConnector();
        // 可按需配置连接超时等参数
        connector.setConnectTimeout(Duration.ofSeconds(10));

        return WebClient.builder()
                .clientConnector(connector)
                // 注册自定义的HttpMessageReader
                .codecs(configurer -> configurer.customCodecs().register(new CustomHttpMessageReader()))
                .build();
    }
}

这样就能解决"No suitable default ClientHttpConnector found"异常,同时用Tomcat作为WebClient的底层客户端。

3. 同步使用WebClient的注意事项

即使要同步使用WebClient,也必须遵守:

  • 所有内部Reactive组件(包括HttpMessageReader)必须保持非阻塞,不能在Reactor线程内做阻塞操作
  • 仅在非Reactive业务线程(比如Spring MVC的Controller线程)中调用block()/blockOptional(),避免在Reactor非阻塞线程中触发阻塞

内容的提问来源于stack exchange,提问作者Younes El Ouarti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 09:55:10