为不同的配置文件定义具有相同方法名称的 Spring Bean

dan*_*niu 8 java spring

我有一个配置类,根据所选配置文件定义了两个 bean,并覆盖了配置方法:

@Configuration
class MyConfig {
    @Profile("profile")
    @Bean
    MyBean myBean(MyBeanProperties properties) {
        return new MyBean(properties);
    }

    @Profile("!profile")
    @Bean
    MyBean myBean(MyBeanProperties properties, AdditionalProperties addProps) {
        MyBean result = new MyBean(properties);
        result.addAdditionalProperties(addProps);
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

MyBean和一个自动装配到其中的类

@Service
class MyService {
     MyBean autowiredBean;
     private MyService(MyBean bean) { this.autowiredBean = bean; }
}
Run Code Online (Sandbox Code Playgroud)

现在,当我启动 Spring 上下文时,它失败并显示消息

com.example.MyServce 中构造函数的参数 0 需要类型为“com.example.MyBean”的 bean,但无法找到。

这怎么可能?我清楚地定义了 Spring bean,因此它应该在创建上下文时出现。

dan*_*niu 10

原因是 Spring 由于配置方法名称而认为这些 bean 具有相同的名称,因此无法实例化它们(尽管在任何给定的活动 Profile 中只应创建一个)。这会工作得很好:

@Configuration
class MyConfig {
    @Profile("profile")
    @Bean
    MyBean myBean(MyBeanProperties properties) {
        return new MyBean(properties);
    }

    @Profile("!profile")
    @Bean
    // note different method name
    MyBean otherBean(MyBeanProperties properties, AdditionalProperties addProps) {
        MyBean result = new MyBean(properties);
        result.addAdditionalProperties(addProps);
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

我没有在任何地方找到这种行为的解释,所以我发布了这个自我回答的问题来分享。

我在现实生活中遇到过这种情况,WebClient它是通过一个配置文件中的客户端注册来实例化的,而另一个配置文件中没有客户端注册(因为创建交换过滤器不需要任何客户端注册)。


小智 6

这是因为当两个 bean 定义了相同的方法名称并且其中一个 bean 预计会根据某种条件(在本例中基于配置文件)被跳过时会导致这种情况。在本例中,“myBean”使用不同的配置文件定义了两次。

解析配置类的方式是迭代该类中的所有 beanMethods 并添加相应的 bean 定义。迭代是按照 beanMethods 在配置类中定义的顺序进行的。这是代码的链接。

根据这些 bean 在配置类中定义的顺序,如果根据配置文件注释预计将跳过定义的第一个 bean,则 beanMethod 名称将添加到“要跳过”beanMethod 的列表中。这是代码的链接。

现在,当它遇到第二个同名的 bean 时,它会发现该 beanMethod 名称已经存在于“要跳过的”方法列表中,因此即使没有固有条件(如配置文件)也会跳过该 bean这会导致它被跳过。这是代码的链接。

您会注意到,如果交换 Bean 的顺序并使用之前失败的相同配置文件来运行,该 Bean 将被拾取。

在配置类中使用唯一的 beanMethod 名称将是避免这种情况的最佳方法。