Spring Boot中WebClient.Builder添加@LoadBalanced引发循环依赖问题
项目背景
- 基于Spring Boot 3.0.5的微服务架构,采用注册中心管理服务,通过Zipkin实现分布式追踪
- 引入的核心依赖:
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId> </dependency> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-brave</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <scope>runtime</scope> </dependency>
根据官方文档说明:
If you create the WebClient or the RestTemplate without using the auto-configured builders, automatic trace propagation won’t work!
因此必须使用自动配置的WebClient.Builder才能实现链路追踪的自动传播。但自动配置的WebClient.Builder(来自WebClientAutoConfiguration.class)并未带有@LoadBalanced注解,源码如下:
@Bean @Scope("prototype") @ConditionalOnMissingBean public WebClient.Builder webClientBuilder(ObjectProvider<WebClientCustomizer> customizerProvider) { WebClient.Builder builder = WebClient.builder(); customizerProvider.orderedStream().forEach((customizer) -> { customizer.customize(builder); }); return builder; }
为了给WebClient添加负载均衡能力,我自定义了如下Bean:
@Bean @LoadBalanced public WebClient.Builder webClient(WebClient.Builder builder) { return builder; }
但这引发了循环依赖问题:
The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | webClient defined in class path resource [...//WebClientConfig.class] └─────┘
我可以通过为WebClient/WebClient.Builder编写包装类来解决,但这种方式过于繁琐(因为包含大量方法)。请问该循环依赖问题的原因是什么?如何合理解决?
循环依赖原因
当你自定义@Bean @LoadBalanced WebClient.Builder时,Spring会尝试注入WebClient.Builder作为参数。但自动配置的webClientBuilder Bean带有@ConditionalOnMissingBean注解——Spring发现已经存在一个WebClient.Builder类型的Bean(就是你自定义的这个),所以不会创建自动配置的实例。这就导致你的自定义Bean需要依赖自身,形成了循环依赖。
合理解决方法
方法1:通过@Autowired列表绑定自动配置Builder并添加负载均衡(推荐)
不需要重新定义WebClient.Builder Bean,而是利用Spring的自动配置机制,给自动生成的Builder加上负载均衡标识:
@Configuration public class WebClientConfig { @LoadBalanced @Autowired private List<WebClient.Builder> webClientBuilders; // 按需创建带负载均衡的WebClient实例 @Bean public WebClient myLoadBalancedWebClient() { return webClientBuilders.get(0) // 可添加自定义配置,比如默认header、超时时间等 .build(); } }
这种方式既保留了链路追踪的自动配置(自动应用WebClientCustomizer),又给Builder赋予了负载均衡能力,是最简洁的方案。
方法2:手动整合负载均衡与链路追踪自定义器
如果需要完全自定义Builder,可以手动注入负载均衡过滤器和所有自动配置的WebClientCustomizer,直接构建符合要求的Builder:
@Configuration public class WebClientConfig { @Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder( LoadBalancerExchangeFilterFunction loadBalancerFilter, ObjectProvider<WebClientCustomizer> customizers) { WebClient.Builder builder = WebClient.builder() .filter(loadBalancerFilter); // 应用所有自动配置的自定义器(包括链路追踪的) customizers.orderedStream().forEach(builder::customize); return builder; } }
这种方式主动整合了负载均衡和链路追踪的能力,避免了循环依赖,同时保留了自动配置的所有特性。
方法3:用@Qualifier指定注入自动配置的Builder
如果坚持要保持原有的自定义Bean形式,可以通过@Qualifier明确指定注入自动配置的webClientBuilder实例(自动配置的Bean名称为webClientBuilder):
@Configuration public class WebClientConfig { @Bean @LoadBalanced public WebClient.Builder webClient(@Qualifier("webClientBuilder") WebClient.Builder builder) { return builder; } }
这种方式打破了循环依赖,让Spring明确知道要注入的是自动配置的Builder,而非自定义的Bean。
内容的提问来源于stack exchange,提问作者Darlyn

