1.3. Bean 概述

预计阅读时间: 17 分钟

Spring 的控制反转(IoC)容器管理着一个或多个 Bean。这些 Bean 是依据你提供给容器的配置元数据(例如,以XML的<bean/> 定义形式)创建出来的。

在容器内部,这些 Bean 定义是以 BeanDefinition 对象来表示的,该对象包含(除其他信息之外)以下元数据:

  • 一个带包限定的类名:通常是正在定义的Bean的实际实现类。

  • Bean 行为配置元素,它们规定了 Bean 在容器中应有的行为方式(作用域、生命周期回调等)。

  • 对该 Bean 完成其工作所需的其他 Bean 的引用。这些引用也被称作协作者或依赖项。

  • 其他在新建对象中需要设置的配置选项——例如,连接池的大小限制或用于管理连接池的 bean 中使用的连接数。

这些元数据会转换为构成每个 Bean 定义的一组属性。下表描述了这些属性:

Table 1. The bean definition

属性解释...
Class实例化 Beans
Name命名 Bean
ScopeBean Scopes
Constructor arguments依赖注入
Properties依赖注入
Autowiring mode自动装配协作者(Autowiring Collaborators)
Lazy initialization mode懒加载的 Beans
Initialization method初始化回调
Destruction method销毁回调

除了包含如何创建特定 bean 的相关信息的 bean 定义之外,ApplicationContext(应用上下文)的各种实现还允许注册那些在容器外部(由用户)创建的已有对象。这是通过 getBeanFactory() 方法来访问 ApplicationContext(应用上下文)的 BeanFactory(bean 工厂)实现的,该方法会返回 DefaultListableBeanFactory(默认可列表 bean 工厂)实现类。DefaultListableBeanFactory(默认可列表 bean 工厂)通过 registerSingleton(..)(注册单例)和 registerBeanDefinition(..)(注册 bean 定义)方法来支持这种注册操作。不过,典型的应用程序通常只使用通过常规 bean 定义元数据所定义的 bean。

TIP

Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他反射步骤中正确处理它们。

虽然在一定程度上支持覆盖现有元数据和现有单例实例,但在运行时注册新 bean(与对工厂的实时访问同时进行)并未得到官方支持

,可能会导致并发访问异常、bean 容器状态不一致,或两者兼有。

1.3.1. Beans 命名

每个 Bean 都有一个或多个标识符。这些标识符必须在托管 Bean 的容器内是唯一的。Bean 通常只有一个标识符。然而,如果需要多个,额外的标识符可以被视为别名。

在基于 XML 的配置元数据中,你可以使用 id 属性、 name 属性或两者同时使用来指定 bean 标识符。 id 属性允许你指定一个确切的 id。传统上,这些名称是字母数字的(如'myBean'、'someService'等),但也可以包含特殊字符。如果你想为 bean 引入其他别名,也可以在 name 属性中指定它们,用逗号( , )、分号( ; )或空格分隔。作为一个历史性的说明,在 Spring 3.1 之前的版本中, id 属性被定义为 xsd:ID 类型,这限制了可能的字符。从 3.1 版本开始,它被定义为 xsd:string 类型。请注意,尽管不再由 XML 解析器强制执行,但 bean id 的唯一性仍然由容器强制执行。

你不需要为 bean 提供 nameid 。如果你没有明确提供 nameid ,容器将为该 bean 生成一个唯一名称。但是,如果你想通过使用 ref 元素或服务定位器风格的查找来通过名称引用该 bean,你必须提供名称。不提供名称的动机与使用内部 bean自动装配协作者(Autowiring Collaborators)相关。

Bean 命名规范

约定在命名 bean 时使用标准的 Java 实例字段命名约定。也就是说,bean 名称以小写字母开头,并从那里开始驼峰式命名。此类名称的示例包括 accountManager 、 accountService 、 userDao 、 loginController 等等。

以一致的方式命名 Bean 会让你的配置更容易阅读和理解。此外,如果你使用 Spring AOP,那么在对一组名称相关的 Bean 应用建议时,也会有很大帮助。

TIP

使用类路径中的组件扫描,Spring 为未命名的组件生成 Bean 名称,遵循之前描述的规则:本质上,将简单类名首字母转换为小写。 然而,在(不寻常的)特殊情况下,当存在多个字符且第一个和第二个字符都是大写时,原始的大小写会被保留。这些规则与 java.beans.Introspector.decapitalize(Spring 在这里使用的)定义的规则相同。

在 Bean Definition 之外为 Bean 命名

在 Bean Definition 本身中,你可以通过使用 id 属性指定的最多一个名称和 name 属性中的任意多个其他名称的组合,为 Bean 提供多个名称。这些名称可以是相同 Bean 的等效别名,并且在某些情况下很有用,例如让应用程序中的每个组件通过使用特定于该组件本身的 Bean 名称来引用公共依赖项。

不过,在实际定义 Bean 的地方指定所有别名并不总是足够的。有时,我们需要为其他地方定义的 bean 引入别名。

在大型系统中,这种情况很常见,因为配置被分割到各个子系统中,每个子系统都有自己的一套对象定义。 有自己的对象定义集。在基于 XML 的配置元数据中,可以使用 <alias/> 元素来实现这一目的。 元素来实现这一目的。下面的示例展示了如何做到这一点:


<alias name="fromName" alias="toName"/>

在这种情况下,一个名为 fromName 的 Bean(在同一容器中)在别名定义使用后,也可以被称为 toName

例如,子系统 A 的配置元数据可能引用名为 subsystemA-dataSource 的数据源。子系统 B 的配置元数据可能引用名为 subsystemB-dataSource 的数据源。当组合使用这两个子系统的主应用程序时,主应用程序引用名为 myApp-dataSource 的数据源。为了使这三个名称都指向同一个对象,你可以在配置元数据中添加以下别名定义:


<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

现在,每个组件和主应用程序都可以通过唯一的名称来引用数据源,并保证不会与任何其他定义冲突(有效地创建了一个命名空间),但它们引用的是同一个 Bean。

Java 配置

如果您使用 Java 配置,可以使用 @Bean 注解来提供别名。请参阅使用 @Bean注解以获取详细信息。

1.3.2. 实例化 Bean

一个 Bean definition 本质上是一个创建一个或多个对象的配方。当容器被请求一个命名 Bean 的配方时,它会使用该 Bean 定义封装的配置元数据来创建(或获取)一个实际的对象。

如果你使用基于 XML 的配置元数据,你将在 <bean/> 元素的 class 属性中指定要实例化的对象的类型(或类)。此 class 属性(在内部,它是 BeanDefinition 实例上的 Class 属性)通常是必需的。(有关例外情况,请参阅使用实例工厂方法进行实例化Bean Definition 继承。)你可以使用 Class 属性以两种方式之一:

  • 通常,在容器直接通过反射调用其构造函数创建 bean 的情况下,指定要构造的 bean 类,这在 Java 代码中相当于使用 new 操作符。
  • 为了指定包含被调用以创建对象的静态工厂方法的实际类,在不太常见的情况下,容器在一个类上调用静态工厂方法来创建 bean。从静态工厂方法的调用返回的对象类型可以是同一个类,也可以完全是另一个类。
嵌套类名

如果您想为嵌套类配置一个 Bean 定义,您可以使用嵌套类的二进制名称或源名称。

例如,如果你有一个名为 SomeThing 的类在 com.example 包中,并且这个 SomeThing 类有一个名为 OtherThing 的静态嵌套类,它们可以通过美元符号( $ )或点( . )分隔。因此,在 bean 定义中, class 属性的值将是 com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing

使用构造函数进行实例化

当你通过构造函数方法创建一个 Bean 时,所有正常类都可以由 Spring 使用和兼容。也就是说,正在开发的类不需要实现任何特定接口或以特定方式编码。

只需指定 Bean 类即可。然而,根据你为该特定 Bean 使用的 IoC 类型,你可能需要一个默认(空)构造函数。

Spring IoC 容器可以管理你希望它管理的任何类。它不仅限于管理真正的 JavaBeans。 大多数 Spring 用户更喜欢只有默认(无参数)构造函数以及根据容器中的属性建模的适当 setter 和 getter 的实际 JavaBeans。你还可以在容器中包含更多异类的非 Bean 样式类。 如果,例如,你需要使用绝对不遵循 JavaBean 规范的旧版连接池,Spring 也可以管理它。

使用基于 XML 的配置元数据,你可以如下指定你的 Bean 类:


<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

关于向构造函数(如有必要)提供参数以及在对象构建后设置对象实例属性的机制详情,请参阅注入依赖

使用静态工厂方法实例化 {#beans-factory-class-static-factory-method

当使用静态工厂方法创建一个 Bean 时,使用 class 属性来指定包含 static 工厂方法的类,并使用名为 factory-method 的属性来指定工厂方法本身的名称。你应该能够调用此方法(如后文所述,带有可选参数)并返回一个活动对象,随后该对象被视为通过构造函数创建的。 这种Bean定义的一个用途是在遗留代码中调用 static 工厂

下面的 Bean 定义指定通过调用工厂方法来创建 Bean。该定义没有指定返回对象的类型(类),而是指定了包含工厂方法的类。在此示例中, createInstance() 方法必须是一个 static 方法。下面的示例展示了如何指定工厂方法:


<bean id="clientService"
      class="examples.ClientService"
      factory-method="createInstance"/>

下面的示例展示了一个可与前面的 Bean 定义配合使用的类:

    public class ClientService {
        private static ClientService clientService = new ClientService();
        private ClientService() {}

        public static ClientService createInstance() {
            return clientService;
        }
    }
    class ClientService private constructor() {
        companion object {
            private val clientService = ClientService()
            @JvmStatic
            fun createInstance() = clientService
        }
    }

关于向工厂方法提供(可选)参数以及在对象从工厂返回后设置对象实例属性的机制详情,请参阅详细依赖和配置

通过实例工厂方法进行实例化

与通过静态工厂方法实例化类似,使用实例工厂方法通过调用容器中现有 bean 的非静态方法来创建新 bean。要使用此机制,请将class 属性留空,并在 factory-bean 属性中指定要调用以创建对象的实例方法的 bean 的名称(位于当前(或父或祖先)容器中)。使用 factory-method 属性设置工厂方法本身的名称。以下示例显示了如何配置此类 bean :

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

        <!-- the bean to be created via the factory bean -->
<bean id="clientService"
      factory-bean="serviceLocator"
      factory-method="createClientServiceInstance"/>

以下示例显示了相应的类:

  public class DefaultServiceLocator {
  
      private static ClientService clientService = new ClientServiceImpl();
  
      public ClientService createClientServiceInstance() {
        return clientService;
      }
  }
  class DefaultServiceLocator {
      companion object {
        private val clientService = ClientServiceImpl()
      }
      fun createClientServiceInstance(): ClientService {
        return clientService
      }
  }

一个工厂类也可以包含多个工厂方法,如下例所示:


<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
      factory-bean="serviceLocator"
      factory-method="createClientServiceInstance"/>

<bean id="accountService"
      factory-bean="serviceLocator"
      factory-method="createAccountServiceInstance"/>

以下示例显示了相应的类:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
        private val accountService = AccountServiceImpl()
    }

    fun createClientServiceInstance(): ClientService {
        return clientService
    }

    fun createAccountServiceInstance(): AccountService {
        return accountService
    }
}

这种方法表明,工厂 bean 本身可以通过依赖注入(DI)进行管理和配置。请参阅详细依赖和配置

TIP

在 Spring 文档中,“factory bean”指的是在 Spring 容器中配置并通过实例静态工厂方法创建对象的 Bean。相比之下,FactoryBean(注意大小写)指的是 Spring 特定的 FactoryBean实现类。

确定一个 Bean 的运行时类型

确定一个特定 bean 的运行时类型不太简单。在 bean 元数据定义中指定的类只是一个初始类引用,可能结合了声明的工厂方法,或者是一个 FactoryBean 类,这可能导致 bean 的运行时类型不同,或者在实例级工厂方法的情况下(通过指定的 factory-bean 名称解决),可能根本未设置。此外,AOP 代理可能会用基于接口的代理包装 bean 实例,这限制了目标 bean 实际类型的暴露(仅限于其实现的接口)。

想要了解特定 bean 的实际运行时类型的做法是对指定 bean 名称进行 BeanFactory.getType 调用。这考虑了上述所有情况,。并返回 BeanFactory.getBean 调用将为同一 Bean 名称返回的对象的类型