Bean 概览
Spring IoC 容器管理一个或多个 bean。这些 bean 使用你提供给容器的配置元数据创建(例如,以 XML <bean/> 定义的形式)。
在容器内部,这些 bean 定义表示为 BeanDefinition 对象,它们包含(除其他信息外)以下元数据:
-
一个包限定的类名:通常是所定义 bean 的实际实现类。
-
Bean 行为配置元素,它说明了 bean 在容器中应如何表现(作用域、生命周期回调等)。
-
bean 完成其工作所需的其他 bean 的引用。这些引用也称为协作器或依赖项。
-
在新创建的对象中设置的其他配置设置 — 例如,池的大小限制或管理连接池的 bean 中使用的连接数。
此元数据转换为构成每个 bean 定义的一组属性。下表描述了这些属性:
| 财产 | 解释于… |
|---|---|
类 |
|
名称 |
|
作用域 |
|
构造函数参数 |
|
属性 |
|
自动装配模式 |
|
延迟初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了包含如何创建特定 bean 的信息的 bean 定义外,ApplicationContext 实现还允许注册在容器外部创建的现有对象(由用户创建)。这是通过访问 ApplicationContext 的 BeanFactory,通过 getAutowireCapableBeanFactory() 方法完成的,该方法返回 DefaultListableBeanFactory 实现。DefaultListableBeanFactory 通过 registerSingleton(..) 和 registerBeanDefinition(..) 方法支持此注册。然而,典型的应用程序只使用通过常规 bean 定义元数据定义的 bean。
|
Bean 元数据和手动提供的单例实例需要尽快注册,以便容器在自动装配和其他内省步骤中正确地推理它们。虽然在一定程度上支持覆盖现有元数据和现有单例实例,但不支持在运行时注册新 bean(与对工厂的实时访问并发),这可能导致并发访问异常、bean 容器中的状态不一致,或两者兼而有之。 |
覆盖 Bean
当使用已分配的标识符注册 bean 时,会发生 bean 覆盖。虽然 bean 覆盖是可能的,但它会使配置更难阅读。
| bean 覆盖将在未来版本中弃用。 |
要完全禁用 bean 覆盖,可以在 ApplicationContext 刷新之前将其 allowBeanDefinitionOverriding 标志设置为 false。在此设置中,如果使用 bean 覆盖,则会抛出异常。
默认情况下,容器会以 INFO 级别记录每次尝试覆盖 bean 的信息,以便您可以相应地调整配置。虽然不推荐,但可以通过将 allowBeanDefinitionOverriding 标志设置为 true 来抑制这些日志。
| 我们承认在测试场景中覆盖 bean 很方便,并且对此有明确的支持。请参阅此部分以获取更多详细信息。 |
命名 Bean
每个 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 定义封装的配置元数据来创建(或获取)实际对象。
如果您使用基于 XML 的配置元数据,您可以在 <bean/> 元素的 class 属性中指定要实例化的对象类型(或类)。这个 class 属性(在内部是 BeanDefinition 实例上的 Class 属性)通常是强制性的。(对于例外情况,请参见使用实例工厂方法进行实例化和Bean 定义继承。)您可以以两种方式使用 Class 属性:
-
通常,用于指定 bean 类,在这种情况下容器本身通过反射调用其构造函数直接创建 bean,这有点类似于使用
new运算符的 Java 代码。 -
在不太常见的情况下,容器调用类上的
static工厂方法来创建 bean,用于指定包含用于创建对象的static工厂方法的实际类。从static工厂方法调用返回的对象类型可能是同一类或完全不同的类。
使用构造函数进行实例化
当您通过构造函数方式创建 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"/>
有关向构造函数提供参数(如果需要)以及在对象构造后设置对象实例属性的机制的详细信息,请参阅注入依赖项。
| 在构造函数参数的情况下,容器可以在几个重载的构造函数中选择一个相应的构造函数。也就是说,为避免歧义,建议使您的构造函数签名尽可能直接。 |
使用静态工厂方法进行实例化
当定义一个使用静态工厂方法创建的 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 及其
|
使用实例工厂方法进行实例化
与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化会调用容器中现有 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 名称返回的对象类型。