Bean 概述
Spring IoC 容器管理一个或多个 Bean。这些 Bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)。
在容器本身中,这些 Bean 定义表示为 BeanDefinition
对象,其中包含(除其他信息外)以下元数据
-
包限定的类名:通常是正在定义的 Bean 的实际实现类。
-
Bean 行为配置元素,它说明 Bean 应该如何在容器中运行(范围、生命周期回调等)。
-
对 Bean 完成其工作所需的其它 Bean 的引用。这些引用也称为协作者或依赖项。
-
在新建对象中设置的其他配置设置——例如,管理连接池的 Bean 中池的大小限制或要使用的连接数。
这些元数据转化为一组属性,构成每个 Bean 定义。下表描述了这些属性
属性 | 在…中解释 |
---|---|
类 |
|
名称 |
|
范围 |
|
构造函数参数 |
|
属性 |
|
自动装配模式 |
|
延迟初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了包含有关如何创建特定 Bean 的信息的 Bean 定义之外,ApplicationContext
实现还允许注册在容器外部(由用户)创建的现有对象。这是通过通过 getBeanFactory()
方法访问 ApplicationContext
的 BeanFactory
来完成的,该方法返回 DefaultListableBeanFactory
实现。DefaultListableBeanFactory
通过 registerSingleton(..)
和 registerBeanDefinition(..)
方法支持此注册。但是,典型的应用程序仅使用通过常规 Bean 定义元数据定义的 Bean。
Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤期间正确地推断它们。虽然在一定程度上支持覆盖现有元数据和现有单例实例,但运行时注册新 Bean(与对工厂的实时访问同时进行)不受官方支持,并且可能导致并发访问异常、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 的配置元数据可以将 DataSource 称为 subsystemA-dataSource
。子系统 B 的配置元数据可以将 DataSource 称为 subsystemB-dataSource
。在组合使用这两个子系统的应用程序时,主应用程序将 DataSource 称为 myApp-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
工厂方法的实际类,在容器调用类上的static
工厂方法来创建 Bean 的不太常见的情况下。从static
工厂方法调用返回的对象类型可能是同一个类或完全不同的类。
使用构造函数实例化
当您通过构造函数方法创建 Bean 时,所有普通类都可以被 Spring 使用并与 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,它有许多
|
使用实例工厂方法实例化
与通过 静态工厂方法 实例化类似,使用实例工厂方法实例化会调用容器中现有 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 名称将返回的对象类型。