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

Spring Boot 3.2.3升级后WebFlux出现Config Bean重复定义问题求助

Spring Boot 3.2.3升级后WebFlux多模块应用出现重复Config Bean问题

问题背景

将Spring Boot版本从3.1.6升级到3.2.3后,原有正常运行的WebFlux多模块应用启动失败,应用结构如下:

  • config模块:包含读取application.properties配置的Config.java
  • webservice模块:包含Controller.java,依赖config模块
  • compute模块:包含Compare.java和ClientAPI.java,依赖config模块

启动错误信息

14:57:56.453 INFO  m.ClientConfig - WebClient Creation init
14:57:56.668 WARN  o.s.b.w.r.c.AnnotationConfigReactiveWebServerApplicationContext - Exception encountered during context initialization - 
cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'controller': 
Unsatisfied dependency expressed through field 'compare': Error creating bean with name 'compare': 
Unsatisfied dependency expressed through field 'clientAPI': Error creating bean with name 'clientAPI' 
defined in file [C:\git\project\compute\target\classes\mypackage\service\ClientAPI.class]: 
Unsatisfied dependency expressed through constructor parameter 1: No qualifying bean of type 'mypackage.config.Config'
available: expected single matching bean but found 2: config,mypackage.config.Config
14:57:56.698 INFO  o.s.b.a.l.ConditionEvaluationReportLogger - 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
14:57:56.731 ERROR o.s.b.d.LoggingFailureAnalysisReporter - 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 1 of constructor in mypackage.service.ClientAPI required a single bean, but 2 were found:
    - config: defined in file [C:\git\project\config\target\classes\mypackage\config\Config.class]
    - mypackage.config.Config: defined in unknown location

This may be due to missing parameter name information

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

相关代码片段

Controller.java

import mypackage.service.Compare;
import mypackage.config.Config;

@RestController
@RequestMapping("/v1")
public class Controller {

    @Autowired
    Compare compare;

    @Autowired
    Config config;

    private static final String COMPARE_PATH_SERVICE = "/compare";

    @PostMapping(path = COMPARE_PATH_SERVICE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<Response> getResponse(@RequestHeader("requestId") String requestId,
            @RequestBody @Valid InputData inputData) {
                if (config.getMyPersonalKey().equalsIgnoreCase("value")){
                    return compare.compute(requestId, inputData);
                } else {
                    // 省略其他逻辑
                }

    }
}

Compare.java

package mypackage.service;
import mypackage.config.Config;

@Service
public class Compare {

    @Autowired
    ClientAPI clientAPI;

    @Autowired
    Config config;
    
    public Mono<Response> compute(String requestId,@Valid InputData inputData) {
        return clientAPI.sendRequest(requestManagement.buildRequest(requestId, inputData))
                .flatMap(response -> {
                    // 省略其他逻辑
                        return Mono.just(response);
                });
    }

}

ClientAPI.java

(注:下方引用的WebClient由其他模块通过"MyWebClient"限定符创建)

package mypackage.service;
import mypackage.config.Config;

@Service
public class ClientAPI {

    private WebClient webClient;
    private static final String URI = "/subsystem/v1/compare";
    private String url;
    
    @Autowired
    public ClientAPI(@Qualifier("WebClient") WebClient webClient, Config config) {
        this.url = "https://" + config.getServiceHost() + ":"
                + config.getServicePort() + URI;
        this.webClient = webClient;
    }

    public Mono<Response> sendRequest(Request bodyRequest) {
        return this.webClient.post().uri(this.url)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .body(Mono.just(bodyRequest), Request.class).retrieve().bodyToMono(Response.class);
    }

}

Config.java

package mypackage.config;

@ConfigurationProperties
@Component
//@Primary // 取消注释后应用可正常运行
public class Config {

    @Value("${my.personal.key}")
    String myPersonalKey;

    public String getMyPersonalKey() {
        return myPersonalKey;
    }
}

已尝试的解决方法

给Config.java添加@Primary注解后,应用可正常启动,但不清楚为何会出现两个Config Bean实例。

问题原因分析

你的Config类同时使用了@Component和@ConfigurationProperties注解,这在Spring Boot 3.2版本中触发了重复Bean注册的问题:

  1. 第一次注册:@Component注解会被Spring的组件扫描机制识别,将Config类注册为名为config的Bean(对应错误日志中来自config模块类文件的实例)。
  2. 第二次注册:Spring Boot 3.2对@ConfigurationProperties的处理逻辑进行了调整——即使类上已经存在@Component,配置属性绑定机制仍会自动将该类注册为一个独立的Bean,名称为类的全限定名mypackage.config.Config(对应错误日志中"未知位置"的实例)。
  3. 多模块场景下,各个模块的组件扫描范围覆盖到了Config类,进一步触发了这两次注册逻辑,最终导致容器中存在两个同类型的Bean,注入时出现冲突。

优化解决方法

不推荐仅依赖@Primary注解绕过问题,建议统一配置属性类的注册方式:

方法一:移除@Component,使用配置属性扫描

  1. 从Config.java中移除@Component注解,仅保留@ConfigurationProperties:
package mypackage.config;

@ConfigurationProperties
public class Config {

    @Value("${my.personal.key}")
    String myPersonalKey;

    public String getMyPersonalKey() {
        return myPersonalKey;
    }
}
  1. 在应用的主启动类上添加@ConfigurationPropertiesScan注解,指定扫描mypackage.config包:
@SpringBootApplication
@ConfigurationPropertiesScan(basePackages = "mypackage.config")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

方法二:保留@Component,禁用自动注册配置属性Bean

如果需要保留@Component,可以通过配置关闭@ConfigurationProperties的自动注册:
在application.properties中添加:

spring.boot.configurations.auto-registration.enabled=false

不过这种方法不推荐,因为会影响其他配置属性类的自动处理。

内容的提问来源于stack exchange,提问作者cknelle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 10:32:08