Bean概述

Spring IoC 容器管理一个或多个 Bean。这些 Bean 使用您提供给容器的配置元数据创建(例如,以 XML <bean/> 定义的形式)。

在容器本身中,这些 Bean 定义表示为BeanDefinition 对象,其中包含(除其他信息外)以下元数据:

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

  • Bean 行为配置元素,说明 Bean 应如何在容器中运行(范围、生命周期回调等)。

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

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

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

表 1. Bean 定义
属性 解释于…

实例化 Bean

名称

命名 Bean

范围

Bean作用域

构造函数参数

依赖注入

属性

依赖注入

自动装配模式

自动装配协作者

延迟初始化模式

延迟初始化Bean

初始化方法

初始化回调

销毁方法

销毁回调

除了包含有关如何创建特定 Bean 的信息的 Bean 定义外,ApplicationContext 实现还允许注册在容器外部(由用户)创建的现有对象。这是通过通过getBeanFactory() 方法访问 ApplicationContextBeanFactory 来完成的,该方法返回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 类上的任何预先声明的构造函数。

我们承认在测试场景中重写 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 生成一个唯一的名称。但是,如果您想通过名称引用该 Bean,通过使用ref元素或服务定位器样式查找,则必须提供名称。不提供名称的动机与使用内部 Bean自动装配协作者有关。

Bean 命名约定

约定是在命名 Bean 时使用标准 Java 约定作为实例字段名称。也就是说,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。子系统 B 的配置元数据可能将数据源称为subsystemB-dataSource。在组合使用这两个子系统的主应用程序时,主应用程序将数据源称为myApp-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 代码。

  • 在不太常见的情况下,指定包含要调用以创建 bean 的static 工厂方法的实际类。从调用static 工厂方法返回的对象类型可能是同一个类或完全不同的类。

嵌套类名

如果要为嵌套类配置 bean 定义,可以使用嵌套类的二进制名称或源名称。

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

使用构造函数实例化

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

Spring IoC 容器可以管理您想要管理的几乎任何类。它不限于管理真正的 JavaBean。大多数 Spring 用户更喜欢实际的 JavaBean,它们只有一个默认(无参数)构造函数和根据容器中的属性建模的适当的 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 名称将返回的对象类型。