使用 @Bean 注解

@Bean 是一个方法级注解,是 XML <bean/> 元素的直接模拟。 该注解支持 <bean/> 提供的一些属性,例如:

您可以在 @Configuration 注解的类或 @Component 注解的类中使用 @Bean 注解。

声明 Bean

要声明 bean,您可以使用 @Bean 注解对方法进行注解。 您使用此方法在 ApplicationContext 中注册一个 bean 定义,其类型指定为方法的返回值。 默认情况下,bean 名称与方法名称相同。以下示例显示了 @Bean 方法声明:

  • Java

@Configuration
public class AppConfig {

  @Bean
  public TransferServiceImpl transferService() {
    return new TransferServiceImpl();
  }
}

前面的配置完全等同于以下 Infra XML:

<beans>
  <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都在 ApplicationContext 中提供了一个名为 transferService 的 bean, 绑定到类型为 TransferServiceImpl 的对象实例,如下所示:

transferService -> com.acme.TransferServiceImpl

您还可以使用默认方法来定义 bean。这允许通过在默认方法上实现带有 bean 定义的接口来组合 bean 配置。

  • Java

public interface BaseConfig {

  @Bean
  default TransferServiceImpl transferService() {
    return new TransferServiceImpl();
  }
}

@Configuration
public class AppConfig implements BaseConfig {

}

您还可以使用接口(或基类)返回类型声明您的 @Bean 方法,如下例所示:

  • Java

@Configuration
public class AppConfig {

  @Bean
  public TransferService transferService() {
    return new TransferServiceImpl();
  }
}

但是,这将高级类型预测的可见性限制为指定的接口类型(TransferService)。 然后,只有一旦实例化的受影响的单例 bean,容器才知道完整类型(TransferServiceImpl)。 非延迟单例 bean 根据其声明顺序实例化,因此您可能会看到不同的类型匹配结果, 具体取决于另一个组件何时尝试通过未声明的类型进行匹配(例如 @Autowired TransferServiceImpl,它仅在 transferService bean 实例化后才解析)。

如果您始终通过声明的服务接口引用您的类型,您的 @Bean 返回类型可以安全地加入该设计决策。 但是,对于实现多个接口的组件或可能由其实陈类型引用的组件,声明尽可能具体的返回类型更安全(至少与引用您的 bean 的注入点所要求的具体程度相同)。

Bean 依赖项

@Bean 注解的方法可以有任意数量的参数,描述构建该 bean 所需的依赖项。 例如,如果我们的 TransferService 需要一个 AccountRepository,我们可以通过方法参数具体化该依赖项,如下例所示:

  • Java

@Configuration
public class AppConfig {

  @Bean
  public TransferService transferService(AccountRepository accountRepository) {
    return new TransferServiceImpl(accountRepository);
  }
}

解析机制与基于构造函数的依赖注入几乎相同。 有关更多详细信息,请参阅 相关部分

接收生命周期回调

任何使用 @Bean 注解定义的类都支持常规生命周期回调,并可以使用 JSR-250 中的 @PostConstruct@PreDestroy 注解。 有关更多详细信息,请参阅 JSR-250 注解

完全支持常规 Infra 生命周期 回调。 如果 bean 实现 InitializingBeanDisposableBeanLifecycle,则容器会调用它们各自的方法。

也完全支持标准集 *Aware 接口(例如 BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware 等)。

@Bean 注解支持指定任意初始化和销毁回调方法,就像 Infra XML 中 bean 元素上的 init-methoddestroy-method 属性一样,如下例所示:

  • Java

public class BeanOne {

  public void init() {
    // 初始化逻辑
  }
}

public class BeanTwo {

  public void cleanup() {
    // 销毁逻辑
  }
}

@Configuration
public class AppConfig {

  @Bean(initMethod = "init")
  public BeanOne beanOne() {
    return new BeanOne();
  }

  @Bean(destroyMethod = "cleanup")
  public BeanTwo beanTwo() {
    return new BeanTwo();
  }
}

默认情况下,使用具有公共 closeshutdown 方法的 Java 配置定义的 bean 会自动登记销毁回调。 如果您有一个公共 closeshutdown 方法,并且不希望在容器关闭时调用它,您可以将 @Bean(destroyMethod = "") 添加到您的 bean 定义中以禁用默认 (inferred) 模式。

对于使用 JNDI 获取的资源,您可能希望默认执行此操作,因为其生命周期在应用程序之外管理。 特别是,对于 DataSource,请务必始终执行此操作,因为已知它在 Jakarta EE 应用服务器上存在问题。

以下示例显示了如何防止 DataSource 的自动销毁回调:

  • Java

@Bean(destroyMethod = "")
public DataSource dataSource() throws NamingException {
  return (DataSource) jndiTemplate.lookup("MyDS");
}

此外,对于 @Bean 方法,您通常使用编程式 JNDI 查找,要么使用 Infra JndiTemplateJndiLocatorDelegate 助手, 要么直接使用 JNDI InitialContext,但不使用 JndiObjectFactoryBean 变体(这将迫使您将返回类型声明为 FactoryBean 类型而不是实际目标类型,从而使其难以用于打算引用此处提供的资源的其他 @Bean 方法中的交叉引用调用)。

在上述注释之前的示例中的 BeanOne 的情况下,在构造期间直接调用 init() 方法同样有效,如下例所示:

  • Java

@Configuration
public class AppConfig {

  @Bean
  public BeanOne beanOne() {
    BeanOne beanOne = new BeanOne();
    beanOne.init();
    return beanOne;
  }

  // ...
}
当您直接在 Java 中工作时,您可以对对象执行任何操作,并且不总是需要依赖容器生命周期。

指定 Bean 作用域

Infra 包含 @Scope 注解,以便您可以指定 bean 的作用域。

使用 @Scope 注解

您可以指定使用 @Bean 注解定义的 bean 应具有特定的作用域。 您可以使用 Bean 作用域 部分中指定的任何标准作用域。

默认作用域是 singleton,但您可以使用 @Scope 注解覆盖它,如下例所示:

  • Java

@Configuration
public class MyConfiguration {

  @Bean
  @Scope("prototype")
  public Encryptor encryptor() {
    // ...
  }
}

@Scopescoped-proxy

Infra 提供了一种通过 作用域代理 处理作用域依赖项的便捷方式。 在使用 XML 配置时创建此类代理的最简单方法是 <aop:scoped-proxy/> 元素。 在 Java 中使用 @Scope 注解配置 bean 提供了与 proxyMode 属性等效的支持。 默认值为 ScopedProxyMode.DEFAULT,这通常表示除非在组件扫描指令级别配置了不同的默认值,否则不应创建作用域代理。 您可以指定 ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACESScopedProxyMode.NO

如果您将作用域代理示例从 XML 参考文档(请参阅 作用域代理)移植到使用 Java 的 @Bean,它类似于以下内容:

  • Java

// 作为代理公开的 HTTP Session 作用域 bean
@Bean
@SessionScope
public UserPreferences userPreferences() {
  return new UserPreferences();
}

@Bean
public Service userService() {
  UserService service = new SimpleUserService();
  // 对代理的 userPreferences bean 的引用
  service.setUserPreferences(userPreferences());
  return service;
}

自定义 Bean 命名

默认情况下,配置类使用 @Bean 方法的名称作为生成的 bean 的名称。 但是,可以使用 name 属性覆盖此功能,如下例所示:

  • Java

@Configuration
public class AppConfig {

  @Bean("myThing")
  public Thing thing() {
    return new Thing();
  }
}

Bean 别名

命名 Bean 中所述,有时希望给单个 bean 提供多个名称,也称为 bean 别名。 @Bean 注解的 name 属性为此目的接受一个 String 数组。 以下示例显示了如何为 bean 设置多个别名:

  • Java

@Configuration
public class AppConfig {

  @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
  public DataSource dataSource() {
    // 实例化、配置并返回 DataSource bean...
  }
}

Bean 描述

有时,提供 bean 的更详细文本描述很有帮助。 当 bean 被公开(可能通过 JMX)以进行监控时,这特别有用。

要向 @Bean 添加描述,可以使用 @Description 注解,如下例所示:

  • Java

@Configuration
public class AppConfig {

  @Bean
  @Description("Provides a basic example of a bean")
  public Thing thing() {
    return new Thing();
  }
}