Bean 概述

Spring IoC 容器管理一个或多个 bean。这些 bean 是使用你提供给容器的配置元数据(例如,XML <bean/> 定义的形式)创建的。 在容器内部,这些 bean 定义表示为 BeanDefinition 对象,其中包含(除其他信息外)以下元数据:

  • 包限定的类名:通常是所定义 bean 的实际实现类。

  • Bean 行为配置元素,说明 bean 在容器中应如何表现(作用域、生命周期回调等)。

  • bean 完成其工作所需的其他 bean 的引用。这些引用也称为协作者或依赖项。

  • 在新创建的对象中设置的其他配置设置 — 例如,连接池中使用的池大小限制或连接数,该 bean 管理一个连接池。

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

Table 1. Bean 定义
属性 解释于…​

Class

实例化 Bean

Name

命名 Bean

Scope

Bean 作用域

Constructor arguments

依赖注入

Properties

依赖注入

Autowiring mode

自动装配协作者

Lazy initialization mode

延迟初始化 Bean

Initialization method

初始化回调

Destruction method

销毁回调

除了包含如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext 实现还允许注册在容器外部创建的现有对象(由用户创建)。这是通过 getAutowireCapableBeanFactory() 方法访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 DefaultListableBeanFactory 实现。DefaultListableBeanFactory 通过 registerSingleton(..)registerBeanDefinition(..) 方法支持此注册。但是,典型的应用程序只使用通过常规 bean 定义元数据定义的 bean。

Bean 元数据和手动提供的单例实例需要尽快注册,以便容器在自动装配和其他内省步骤中正确地推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但在运行时(与对工厂的实时访问并发)注册新 bean 尚未得到官方支持,并且可能导致并发访问异常、bean 容器中的状态不一致,或两者兼而有之。

覆盖 Bean

当使用已分配的标识符注册 bean 时,会发生 bean 覆盖。虽然 bean 覆盖是可能的,但它会使配置更难阅读。

Bean 覆盖将在未来的版本中弃用。

要完全禁用 bean 覆盖,可以在 ApplicationContext 刷新之前将其 allowBeanDefinitionOverriding 标志设置为 false。在此设置中,如果使用 bean 覆盖,则会抛出异常。

默认情况下,容器以 INFO 级别记录每次覆盖 bean 的尝试,以便你可以相应地调整配置。虽然不推荐,但你可以通过将 allowBeanDefinitionOverriding 标志设置为 true 来静默这些日志。

Java 配置

如果你使用 Java 配置,只要 @Bean 方法的返回类型与该 bean 类匹配,相应的 @Bean 方法总是会静默覆盖具有相同组件名称的扫描 bean 类。这仅仅意味着容器将调用 @Bean 工厂方法,而不是 bean 类上任何预先声明的构造函数。

我们承认在测试场景中覆盖 bean 很方便,并且从 Spring Framework 6.2 开始对此有明确的支持。请参阅 此部分 了解更多详细信息。

命名 Bean

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

在基于 XML 的配置元数据中,你使用 id 属性、name 属性或两者来指定 bean 标识符。id 属性允许你指定一个 id。按照惯例,这些名称是字母数字('myBean'、'someService' 等),但它们也可以包含特殊字符。如果你想为 bean 引入其他别名,你也可以在 name 属性中指定它们,用逗号 (,)、分号 (;) 或空格分隔。尽管 id 属性定义为 xsd:string 类型,但 bean id 的唯一性由容器强制执行,而不是由 XML 解析器强制执行。

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

Bean 命名约定

约定是使用标准 Java 实例字段命名约定来命名 bean。也就是说,bean 名称以小写字母开头,然后是驼峰命名法。此类名称的示例包括 accountManageraccountServiceuserDaologinController 等。 一致地命名 bean 可以使你的配置更易于阅读和理解。 此外,如果你使用 Spring AOP,它在将通知应用于一组按名称相关的 bean 时非常有帮助。

在类路径中进行组件扫描时,Spring 会为未命名组件生成 bean 名称,遵循前面描述的规则:本质上,采用简单的类名并将其首字母转换为小写。但是,在(不寻常的)特殊情况下,如果有一个以上的字符,并且第一个和第二个字符都是大写,则保留原始大小写。这些规则与 java.beans.Introspector.decapitalize(Spring 在此处使用)定义的规则相同。

在 Bean 定义之外为 Bean 设置别名

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

在实际定义 bean 的地方指定所有别名并不总是足够的。有时需要为在其他地方定义的 bean 引入别名。这在大型系统中很常见,其中配置在每个子系统之间拆分,每个子系统都有自己的一组对象定义。 在基于 XML 的配置元数据中,你可以使用 <alias/> 元素来完成此操作。以下示例展示了如何执行此操作:

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

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

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

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

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

Java 配置

如果你使用 Java 配置,@Bean 注解可用于提供别名。 有关详细信息,请参阅 使用 @Bean 注解

实例化 Bean

bean 定义本质上是创建一个或多个对象的“食谱”。当被要求时,容器会查看命名 bean 的“食谱”,并使用该 bean 定义封装的配置元数据来创建(或获取)一个实际对象。

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

  • 通常,指定要构造的 bean 类,在这种情况下,容器本身通过反射调用其构造函数直接创建 bean,这有点类似于使用 new 运算符的 Java 代码。

  • 在不那么常见的情况下,指定包含被调用以创建对象的 static 工厂方法的实际类,在这种情况下,容器调用类上的 static 工厂方法来创建 bean。从 static 工厂方法调用返回的对象类型可能与该类相同,也可能完全是另一个类。

嵌套类名

如果你想为嵌套类配置 bean 定义,你可以使用嵌套类的二进制名称或源名称。 例如,如果你在 com.example 包中有一个名为 SomeThing 的类,并且此 SomeThing 类有一个名为 OtherThingstatic 嵌套类,它们可以用美元符号 ($) 或点 (.) 分隔。因此,bean 定义中 class 属性的值将是 com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing

使用构造函数进行实例化

当你通过构造函数方式创建 bean 时,所有普通类都可以被 Spring 使用并兼容。也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式编码。只需指定 bean 类即可。但是,根据你为该特定 bean 使用的 IoC 类型,你可能需要一个默认的(空)构造函数。

Spring IoC 容器几乎可以管理你希望它管理的任何类。它不限于管理真正的 JavaBeans。大多数 Spring 用户更喜欢实际的 JavaBeans,它们只包含一个默认的(无参数)构造函数以及根据容器中的属性建模的适当的 setter 和 getter。你还可以在容器中拥有更奇特的非 bean 样式类。例如,如果你需要使用一个完全不符合 JavaBean 规范的遗留连接池,Spring 也可以管理它。

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

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

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

有关提供构造函数参数(如果需要)以及在对象构造后设置对象实例属性的机制的详细信息,请参阅 注入依赖项

在构造函数参数的情况下,容器可以在多个重载构造函数中选择一个相应的构造函数。也就是说,为了避免歧义,建议尽可能保持构造函数签名简单明了。

使用静态工厂方法进行实例化

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

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

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

以下示例显示了一个与前面 bean 定义一起使用的类:

  • Java

  • Kotlin

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
	}
}

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

在工厂方法参数的情况下,容器可以在多个同名重载方法中选择一个相应的方法。也就是说,为了避免歧义,建议尽可能保持工厂方法签名简单明了。

Mockito 及其许多 mock 方法重载是一个典型的工厂方法重载问题。选择最具体的 mock 变体:

<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
	<constructor-arg type="java.lang.Class" value="examples.ClientService"/>
	<constructor-arg type="java.lang.String" value="clientService"/>
</bean>

使用实例工厂方法进行实例化

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

<!-- the factory bean, which contains a method called createClientServiceInstance() -->
<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"/>

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

  • Java

  • Kotlin

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"/>

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

  • Java

  • Kotlin

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) 进行管理和配置。 请参阅 依赖项和配置详情

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

确定 Bean 的运行时类型

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

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