WebClient POST请求jsonplaceholder报UnknownHostException求助
问题根因
这个报错和公网连通性、WebClient基础写法没有关系,核心原因是Eureka环境下的负载均衡逻辑劫持了WebClient的域名解析流程:
- 你代码里手动
new RestTemplate()能正常请求公网接口,是因为这个实例是直接实例化的,没有被Spring加上@LoadBalanced对应的负载均衡拦截器,走的是系统默认公网DNS解析逻辑。 - 你的服务已经集成了Netflix Eureka和Spring Cloud负载均衡组件,只要类路径下存在WebClient依赖,Spring Cloud的自动配置会默认给WebClient的构造链注入负载均衡过滤器。这个过滤器的逻辑是把请求URI里的主机名默认当作Eureka注册中心里的服务ID,直接去注册中心拉取服务实例做负载均衡,完全跳过公网DNS解析。公网域名
jsonplaceholder.typicode.com根本没有在你的Eureka集群注册,自然会抛出域名解析失败的异常。
解决方案
根据使用场景二选一即可:
方案1:拆分内外网专用WebClient实例(生产环境推荐)
不要直接调用全局WebClient.builder()构造所有WebClient实例,单独定义两类不同用途的Builder Bean,从根源上隔离内网服务调用和外网请求的逻辑:
- 在配置类中声明两个带不同标识的WebClient.Builder:
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; @Configuration public class WebClientConfig { // 用于调用Eureka集群内注册的服务,自动带负载均衡能力 @Bean @LoadBalanced public WebClient.Builder lbWebClientBuilder() { return WebClient.builder(); } // 用于调用公网/第三方外部服务,不加载负载均衡逻辑 @Bean public WebClient.Builder externalWebClientBuilder() { return WebClient.builder(); } }
- 调用公网接口时,注入专门用于外部请求的
externalWebClientBuilder构造WebClient,就会走正常公网DNS解析:
@Autowired private WebClient.Builder externalWebClientBuilder; // 构造请求客户端 WebClient client = externalWebClientBuilder .baseUrl("https://jsonplaceholder.typicode.com") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build();
方案2:临时构造实例时手动移除负载均衡过滤器
如果只是临时测试,不想额外定义配置Bean,可以在手动构造WebClient的时候,直接把注入的负载均衡过滤器移除即可:
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerExchangeFilterFunction; WebClient client = WebClient.builder() .baseUrl("https://jsonplaceholder.typicode.com") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) // 移除自动注入的负载均衡过滤器 .filters(filterList -> filterList.removeIf( filter -> filter.getClass().equals(LoadBalancerExchangeFilterFunction.class) )) .build();
额外优化建议
你当前手写JSON字符串传参的写法存在冗余,还容易出现转义错误。既然已经配置了JSON消息转换器,直接传原生Java/Map对象即可,WebClient会自动完成序列化:
// 不用手动拼JSON字符串,直接构造参数 Map<String, Object> reqBody = Map.of( "title", "foo", "body", "bar", "userId", 1 ); Mono<String> respMono = client.post() .uri("/posts") .bodyValue(reqBody) .retrieve() .bodyToMono(String.class); String resp = respMono.block();
内容的提问来源于stack exchange,提问作者Ezani
相关产品推荐
相关产品推荐

