|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
Bean 概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是通过您提供给容器的配置元数据创建的(例如,以 XML <bean/> 定义的形式)。
在容器内部,这些 Bean 定义以 BeanDefinition 对象的形式表示,其中包含(除其他信息外)以下元数据:
-
一个带包限定的类名:通常是所定义 Bean 的实际实现类。
-
Bean 行为配置元素,用于声明 Bean 在容器中应如何行为(作用域、生命周期回调等)。
-
对其他 Bean 的引用,这些 Bean 是当前 Bean 完成其工作所必需的。这些引用也被称为协作者(collaborators)或依赖项(dependencies)。
-
在新创建的对象中需要设置的其他配置项——例如,连接池的大小限制,或用于管理连接池的 bean 所使用的连接数量。
此元数据转换为一组属性,这些属性构成了每个 bean 的定义。 下表描述了这些属性:
| 属性 | 详见…… |
|---|---|
类 |
|
姓名 |
|
作用域 |
|
构造函数参数 |
|
属性 |
|
自动装配模式 |
|
延迟初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了包含如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext 的实现还允许注册在容器外部(由用户)创建的现有对象。这是通过 BeanFactory 方法访问 getBeanFactory() 的 DefaultListableBeanFactory 来实现的,该方法返回 DefaultListableBeanFactory 的实现。registerSingleton(..) 通过 registerBeanDefinition(..) 和 7 方法支持此类注册。然而,典型的应用程序仅使用通过常规 bean 定义元数据定义的 bean。
|
Bean 的元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中能够正确地对它们进行推理。尽管在一定程度上支持覆盖已有的元数据和已存在的单例实例,但在运行时(与工厂的活跃访问并发进行时)注册新的 Bean 并未得到官方支持,可能会导致并发访问异常、Bean 容器状态不一致,或两者兼而有之。 |
覆盖 Bean
当使用一个已被占用的标识符注册 Bean 时,就会发生 Bean 覆盖。虽然 Bean 覆盖是可能的,但它会使配置更难阅读,此功能将在未来的版本中被弃用。
要完全禁用 Bean 覆盖,可以在刷新 allowBeanDefinitionOverriding 之前将其 false 标志设置为 ApplicationContext。在此配置下,如果使用了 Bean 覆盖,将抛出异常。
默认情况下,容器会以 INFO 级别记录每个被覆盖的 bean,以便您可以相应地调整配置。虽然不建议这样做,但您可以通过将 allowBeanDefinitionOverriding 标志设置为 true 来关闭这些日志。
命名 Bean
每个 Bean 都有一个或多个标识符。这些标识符在其所在的容器内必须是唯一的。通常,一个 Bean 只有一个标识符。然而,如果它需要多个标识符,则额外的标识符可视为别名。
在基于 XML 的配置元数据中,您可以使用 id 属性、name 属性或两者来指定 Bean 标识符。id 属性允许您精确指定一个 id。按照惯例,这些名称由字母数字组成(例如 'myBean'、'someService' 等),但也可以包含特殊字符。如果您想为 Bean 引入其他别名,还可以在 name 属性中指定它们,使用逗号 (,)、分号 (;) 或空白字符进行分隔。尽管 id 属性被定义为 xsd:string 类型,但 Bean id 的唯一性是由容器强制执行的,而非由 XML 解析器执行。
您无需为 Bean 提供 name 或 id。如果您未显式提供 name 或 id,容器将为该 Bean 生成一个唯一的名称。但是,如果您希望通过使用 ref 元素或服务定位器风格的查找来按名称引用该 Bean,则必须提供一个名称。
不提供名称的动机与使用 内部 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。
实例化 Bean
Bean 定义本质上是用于创建一个或多个对象的配方。 当容器被要求提供某个命名的 Bean 时,它会查看该 Bean 的配方,并使用该 Bean 定义所封装的配置元数据来创建(或获取)一个实际的对象。
如果您使用基于 XML 的配置元数据,则需要在 <bean/> 元素的 class 属性中指定要实例化的对象类型(或类)。此 class 属性(在内部是 BeanDefinition 实例上的 Class 属性)通常是必需的。(有关例外情况,请参阅使用实例工厂方法进行实例化和Bean 定义继承。)
您可以按以下两种方式之一使用 Class 属性:
-
通常,用于指定在容器通过反射调用构造函数直接创建 bean 时所要构造的 bean 类,这在某种程度上等同于使用
new运算符的 Java 代码。 -
在较为少见的情况下,当容器通过调用某个类的
static工厂方法来创建 bean 时,需指定包含该static工厂方法的实际类。通过调用static工厂方法所返回的对象类型可以是同一个类,也可以是完全不同的另一个类。
使用构造函数进行实例化
当你通过构造函数方式创建一个 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 将通过调用一个工厂方法来创建。该定义并未指定返回对象的类型(class),而是指定了包含工厂方法的类。在此示例中,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,它拥有许多
|
使用实例工厂方法进行实例化
与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化会调用容器中现有 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 文档中,"factory bean"(工厂 Bean)指的是在 Spring 容器中配置并通过
实例或
静态工厂方法创建对象的 Bean。相比之下,
FactoryBean(注意首字母大写)指的是一个特定于 Spring 的
FactoryBean实现类。 |
确定 Bean 的运行时类型
特定 bean 的运行时类型并不容易确定。在 bean 元数据定义中指定的类仅是一个初始的类引用,它可能与一个声明的工厂方法结合使用,或者本身是一个 FactoryBean 类,这些情况都可能导致 bean 的实际运行时类型有所不同;或者在使用实例级工厂方法的情况下(此时通过指定的 factory-bean 名称进行解析),该类甚至可能根本没有设置。此外,AOP 代理可能会使用基于接口的代理包装 bean 实例,从而仅暴露目标 bean 实际类型所实现的接口(而非其具体类型)。
要确定特定 bean 的实际运行时类型,推荐的方法是针对指定的 bean 名称调用 BeanFactory.getType。该方法会考虑上述所有情况,并返回与对相同 bean 名称调用 BeanFactory.getBean 所得到的对象类型。