自定义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
相关产品推荐
相关产品推荐

