本文翻译自Spring @Configuration vs @Component

前一篇文章中,我说过我们可以使用@Component作为@Configuration的一个备用选择,实际上这是来自于Spring team的官方建议

也就是说,@Bean在我们不使用任何CGLIB代理时有一种精简模式:只需在未使用@Configuration注释的类上声明@Bean方法(但通常需要用另一种Spring中的stereotype实现替代,如@Component),只要我们不在 @Bean 方法之间进行编程调用,程序就能正常工作。

简而言之,下面显示的每个应用程序上下文配置将以完全不同的方式来生效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Configuration
public static class Config {

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }

    @Bean
    public SimpleBeanConsumer simpleBeanConsumer() {
        return new SimpleBeanConsumer(simpleBean());
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Component
public static class Config {

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }

    @Bean
    public SimpleBeanConsumer simpleBeanConsumer() {
        return new SimpleBeanConsumer(simpleBean());
    }
}

第一段代码能按照预期正常工作,SimpleBeanConsumer将获取一个指向SimpleBean单例的链接,不幸的是,该段代码在Java数字签名环境中不生效

第二个代码则是完全不正确的,因为Spring会创建SimpleBean的单例bean,但是SimpleBeanConsumer会获得另一个SimpleBean实例,该实例不受Spring上下文控制。

此现象的原因解释如下:

如果使用@Configuration,则所有标记为@Bean的方法都将被包装到CGLIB包装器中,该包装器的工作方式为该方法第一次调用时将执行原始方法的主体,并将生成的对象注册在Spring上下文中,之后对该方法的调用都只返回从Spring上下文中获取到的bean。

在上面的第二个代码块中,new SimpleBeanConsumer(simpleBean())只是调用一个纯java方法调用,要更正该代码块,可修改如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Component
public static class Config {
    @Autowired
    SimpleBean simpleBean;

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }

    @Bean
    public SimpleBeanConsumer simpleBeanConsumer() {
        return new SimpleBeanConsumer(simpleBean);
    }
}

这篇文章的所有代码示例都可以在我的个人GitHub中找到。