【问题标题】:Why in Spring I am not allowed to annotate a final class with @Configuration?为什么在 Spring 中我不允许使用 @Configuration 注释最终类?
【发布时间】:2015-05-18 09:59:45
【问题描述】:

我正在学习 Spring Core 认证,我对基于学习材料的这个问题的答案有一些疑问。

为什么不允许使用 @Configuration 注释最终类

我的推理是为了证实这个断言:

考虑以下配置类:

@Bean
public AccountRepository accountRepository() {
    return new JdbcAccountRepository(); 
}

@Bean
public TransferService transferService() {
    TransferServiceImpl service = new TransferServiceImpl();
    service.setAccountRepository(accountRepository());
    return service;
}

@Bean
public AccountService accountService() {
    return new AccountServiceImpl(accountRepository());
}

乍一看,这种情况可能看起来很奇怪,因为第一个方法 (accountRepository()) 将 JdbcAccountRepository 对象实例化为具有 id=AccountRepository 的 bean strong> 遵循 Spring 默认行为,是一个 singleton

第二个和第三个方法调用 两次 accountRepository() 方法应该再实例化两次 JdbcAccountRepository 对象,这不是可能是因为它是单例的!!!

因此,为了解决这种情况,Spring 使用 基于继承的代理 策略来创建我的配置类的子类(由 注释的@Configuration) 确实如此:

  • 对于每个 bean,在子类中缓存一个实例

  • 子类仅在第一次实例化时调用 super

所以子类是入口点,因为这个子类实现了以下行为:

公共类 AppConfig$$EnhancerByCGLIB$ 扩展 AppConfig {

public AccountRepository accountRepository() {
    // if bean is in the applicationContext
    // return bean
    // else call super.accountRepository() and store bean in context
}

public TransferService transferService() {
    // if bean is in the applicationContext, return bean
    // else call super.transferService() and store bean in context
}

.....................................................
.....................................................
.....................................................
}

所以如果我用 final 注释一个配置类,Spring 就不能有这种行为,因为在 Java 中 一个 final 类不能被子类化

对吗?

使用相同的推理,我是否也可以断言在 Spring 中我不能有一个带有 @Bean 注释的 final 方法

因为,如前面的示例所示,我在启动时创建配置类的子类(代理)时,对于每个 bean,都会在子类中缓存一个实例 如果它是最终的,那是不可能的(但我绝对不确定这个断言)

我错过了什么吗?能给我具体的解释吗?

Tnx

【问题讨论】:

    标签: java spring spring-mvc annotations spring-annotations


    【解决方案1】:

    Spring 为使用 @Configuration 类注释的类创建动态代理。 Spring 使用 CGLIB 来扩展你的类来创建代理。因此,配置类不能是最终的。

    关于accountRepository()被调用两次:

    如果你调用accountRepository() 方法来创建一个实例,它就不再是一个Spring 托管的bean。 Spring 不会知道以这种方式创建的实例。因此,您最终会得到多个 JdbcAccountRepository

    实例

    如果您进行如下配置,您可以保留单例行为:

    @Bean
    public TransferService transferService(JdbcAccountRepository jdbcAcctRepo) {
        TransferServiceImpl service = new TransferServiceImpl();
        service.setAccountRepository(jdbcAcctRepo);
        return service;
    }
    
    @Bean
    public AccountService accountService(JdbcAccountRepository jdbcAcctRepo) {
        return new AccountServiceImpl(jdbcAcctRepo);
    } 
    

    【讨论】:

    • 实际上,如果子类化工作正常,您可以调用其他 bean 方法(如 accountRepository())。然后代理将确定是否有必要创建一个新的 bean 实例或者是否已经存在。
    • 如果配置类不是final,Spring可以子类。而且,与accountRepository() 方法调用相关,Spring 不知道方法内部发生了什么。每次使用 new 运算符时,Spring 都无法在逻辑中挂钩以检查 bean 是否已创建。
    • 即使有点晚了,但我又一次偶然发现了这个答案:Spring当然看不到 accountRepository() 方法中发生了什么。但是 Spring 代理将拦截对 accountRepository() 方法本身的所有调用,并检查当前应用程序上下文中是否已经定义了该类型和名称的 bean。如果是这种情况,则代理返回 bean(并且不执行该方法),如果不是,则调用真正的方法并将返回的 bean 存储在应用程序上下文中。这就是为什么在配置 bean 中多次调用 bean 创建方法是安全的
    • @dunni,你说得对,@Mithun,请参考 java doc 获取 @Bean 注释(方法在文档的 @Configuration 注释类部分)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 2011-11-01
    • 2011-04-05
    • 1970-01-01
    • 1970-01-01
    • 2017-01-07
    相关资源
    最近更新 更多