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

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

声明一个 Bean

为了声明一个 Bean,你可以用 @Bean 注解来注解一个方法。你可以用这个方法在 ApplicationContext 中注册一个 Bean 定义,该类型被指定为该方法的返回值。默认情况下,Bean 的名字和方法的名字是一样的。下面的例子显示了一个 @Bean 方法声明:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public TransferServiceImpl transferService() {
  5. return new TransferServiceImpl();
  6. }
  7. }

前面的配置与下面的 Spring XML 完全等效:

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

这两个声明使 ApplicationContext 中一个名为 transferService 的 Bean 可用,并与 TransferServiceImpl 类型的对象实例绑定,正如下面的文字图片所示:

  1. transferService -> com.acme.TransferServiceImpl

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

  1. public interface BaseConfig {
  2. @Bean
  3. default TransferServiceImpl transferService() {
  4. return new TransferServiceImpl();
  5. }
  6. }
  7. @Configuration
  8. public class AppConfig implements BaseConfig {
  9. }

你也可以用一个接口(或基类)的返回类型来声明你的 @Bean 方法,如下例所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public TransferService transferService() {
  5. return new TransferServiceImpl();
  6. }
  7. }

然而,这将提前类型预测的可见性限制在指定的接口类型(TransferService)。然后,只有在受影响的单例 Bean 被实例化后,容器才知道完整的类型(TransferServiceImpl)。非 lazy 单例 Bean 根据它们的声明顺序被实例化,所以你可能会看到不同的类型匹配结果,这取决于另一个组件何时试图通过非声明的类型进行匹配(比如 @Autowired TransferServiceImpl,它只在 transferService Bean 被实例化后才会解析)。

:::tips 如果你一直通过声明的服务接口来引用你的类型,你的 @Bean 返回类型可以安全地加入这个设计决定。然而,对于实现了多个接口的组件或可能被其实现类型引用的组件来说,声明最具体的返回类型是比较安全的(至少要和引用你的 Bean 的注入点要求的具体类型一样)。 :::

Bean 的依赖性

一个 @Bean 注解的方法可以有任意数量的参数,描述构建该 Bean 所需的依赖关系。例如,如果我们的TransferService 需要一个 AccountRepository,我们可以用一个方法参数将这种依赖关系具体化,如下面的例子所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public TransferService transferService(AccountRepository accountRepository) {
  5. return new TransferServiceImpl(accountRepository);
  6. }
  7. }

解析机制与基于构造函数的依赖注入基本相同。更多细节见相关章节

接收生命周期回调

任何用 @Bean 注解定义的类都支持常规的生命周期回调,并且可以使用 JSR-250 的 @PostConstruct 和 @PreDestroy 注解。更多细节请参见 JSR-250 注解

常规的 Spring 生命周期回调 也被完全支持。如果一个 bean 实现了 InitializingBean、DisposableBean 或 Lifecycle,它们各自的方法就会被容器调用。

标准的 [*Aware](https://www.yuque.com/mrcode.cn/read-docs/tg526d)接口集(如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)也被完全支持。

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

  1. public class BeanOne {
  2. public void init() {
  3. // initialization logic
  4. }
  5. }
  6. public class BeanTwo {
  7. public void cleanup() {
  8. // destruction logic
  9. }
  10. }
  11. @Configuration
  12. public class AppConfig {
  13. @Bean(initMethod = "init")
  14. public BeanOne beanOne() {
  15. return new BeanOne();
  16. }
  17. @Bean(destroyMethod = "cleanup")
  18. public BeanTwo beanTwo() {
  19. return new BeanTwo();
  20. }
  21. }

需要注意的是:

默认情况下,用 Java 配置定义的具有公共 close 或 shutdown 方法的 bean 会自动被列入销毁回调。如果你有一个公共的关闭或关闭方法,并且你不希望它在容器关闭时被调用,你可以在你的 Bean 定义中添加@Bean(destroyMethod="")来禁用默认(推理)模式。

你可能想对你用 JNDI 获取的资源默认这样做,因为它的生命周期是在应用程序之外管理的。特别是,要确保总是对 DataSource 这样做,因为已知它在 Java EE 应用服务器上是有问题的。

下面的示例演示如何防止数据源的自动销毁回调:

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

另外,对于 @Bean 方法,你通常使用程序化的 JNDI 查找,要么使用 Spring 的 JndiTemplate 或 JndiLocatorDelegate 助手,要么直接使用 JNDI InitialContext,但不是 JndiObjectFactoryBean 变体(这将迫使你将返回类型声明为 FactoryBean 类型,而不是实际的目标类型,使得它难以用于其他 @Bean 方法中的交叉引用调用,这些方法打算引用这里提供的资源)。


在前面的例子中的 BeanOne 的情况下,在构造过程中直接调用 init()方法也同样有效,正如下面的例子所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public BeanOne beanOne() {
  5. BeanOne beanOne = new BeanOne();
  6. beanOne.init();
  7. return beanOne;
  8. }
  9. // ...
  10. }

:::tips 当你直接在 Java 中工作时,你可以对你的对象做任何你喜欢的事情,而不一定需要依赖容器的生命周期。 :::

指定 Bean 的作用域

Spring 提供了 @Scope注解,这样你就可以指定 Bean 的作用域。

使用 @Scope 注解

你可以指定你用 @Bean 注解定义的 Bean 应该有一个特定的作用域。你可以使用 Bean Scopes 部分中指定的任何一个标准作用域。

默认的作用域是单例,但你可以用 @Scope 注解来覆盖它,如下例所示:

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean
  4. @Scope("prototype")
  5. public Encryptor encryptor() {
  6. // ...
  7. }
  8. }

@Scope 和 scoped-proxy

Spring 提供了一种通过 作用域代理 来处理作用域依赖的方便方法。在使用 XML 配置时,创建这样一个代理的最简单方法是 <aop:scoped-proxy/>元素。在 Java 中用 @Scope 注解配置你的 Bean,提供了与 proxyMode 属性相当的支持。默认值是 ScopedProxyMode.DEFAULT,这通常表示不应该创建作用域代理,除非在组件扫描指令层配置了不同的默认值。你可以指定 ScopedProxyMode.TARGET_CLASS、ScopedProxyMode.INTERFACES 或 ScopedProxyMode.NO。

如果你把 XML 参考文档中的作用域代理例子(见作用域代理)移植到我们的 @Bean 上,使用 Java,它类似于以下内容:

  1. // 一个以代理形式暴露的 HTTP 会话范围的 Bean
  2. @Bean
  3. @SessionScope
  4. public UserPreferences userPreferences() {
  5. return new UserPreferences();
  6. }
  7. @Bean
  8. public Service userService() {
  9. UserService service = new SimpleUserService();
  10. // 对代理的 userPreferences Bean 的引用。
  11. service.setUserPreferences(userPreferences());
  12. return service;
  13. }

自定义 Bean 命名

默认情况下,配置类使用 @Bean 方法的名称作为结果 Bean 的名称。然而,这个功能可以通过 name 属性来重写,正如下面的例子所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean("myThing")
  4. public Thing thing() {
  5. return new Thing();
  6. }
  7. }

Bean 别名

正如在 命名 Bean 中所讨论的,有时最好给一个 Bean 起多个名字,也就是所谓的 Bean 别名。@Bean 注解的 name 属性接受一个字符串数组来实现这一目的。下面的例子展示了如何为一个 Bean 设置若干别名:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
  4. public DataSource dataSource() {
  5. // 实例化、配置并返回DataSource Bean...
  6. }
  7. }

Bean 描述

有时,为 Bean 提供更详细的文本描述是有帮助的。当 Bean 被暴露(也许是通过 JMX)用于监控目的时,这可能特别有用。

为了给 @Bean 添加描述,你可以使用 @Description 注解,如下例所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. @Description("Provides a basic example of a bean")
  5. public Thing thing() {
  6. return new Thing();
  7. }
  8. }