Bean 概览
一个Spring IoC容器管理一个或多个Bean。这些Bean是根据你提供给容器的配置元数据创建的(例如,以XML <bean/> 定义的形式)。
在容器内部,这些bean定义以BeanDefinition对象的形式存在,这些对象包含(除其他信息外)以下元数据:
-
带有包限定名的类名:通常是指所定义bean的实际实现类。
-
Bean的行为配置元素,这些元素规定了bean在容器中的行为方式(作用域、生命周期回调等)。
-
为了使bean能够正常工作而需要的对其他bean的引用。这些引用也被称为协作者或依赖项。
-
需要在新创建的对象中设置的其他配置选项——例如,池的大小限制,或者管理连接池的bean所使用的连接数。
这些元数据转化为一组属性,构成了每个bean定义的组成部分。下表描述了这些属性:
表1. Bean定义
除了包含如何创建特定bean信息的bean定义之外,ApplicationContext的实现还允许注册在容器外部(由用户)创建的现有对象。这是通过调用ApplicationContext的getAutowireCapableBeanFactory()方法来实现的,该方法会返回DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持这种注册。然而,典型的应用程序仅使用通过常规bean定义元数据定义的bean进行工作。
Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动配置和其他内省步骤中能够正确处理它们。虽然在一定程度上支持覆盖现有的元数据和单例实例,但在运行时注册新Bean(同时还在使用工厂)是不被官方支持的,这可能导致并发访问异常、Bean容器中的状态不一致,或者两者兼有。
覆盖Bean
当使用已被分配的标识符来注册一个bean时,就会发生bean覆盖(bean overriding)现象。虽然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解析器。
当类路径中包含组件扫描功能时,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工厂方法调用返回的对象类型可能是相同的类,也可能是完全不同的另一个类。
使用构造函数进行实例化 {#beans-factory-class-ctor}\
当你通过构造函数的方式来创建bean时,所有普通的类都可以被Spring使用并且与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属性来指定包含该静态工厂方法的类,同时使用名为factory-method的属性来指定工厂方法本身的名称。你应该能够调用这个方法(可以带有可选参数,具体内容稍后介绍),并返回一个实际运行的对象,该对象随后会被当作是通过构造函数创建的一样进行使用。这种Bean定义的一个用途是在旧代码中调用静态工厂方法。
以下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文档中,“factory bean”指的是在Spring容器中配置的bean,它通过实例或静态工厂方法来创建对象。相比之下,FactoryBean(注意首字母大写)指的是Spring特有的FactoryBean实现类。
确定Bean的运行时类型
确定特定Bean的运行时类型并非易事。在Bean元数据定义中指定的类仅是一个初始类引用,可能还会与声明的工厂方法结合使用,或者该类本身就是FactoryBean类,这都可能导致Bean的运行时类型有所不同;而在使用实例级别的工厂方法时(此时通过指定的factory-bean名称来解析),运行时类型可能根本不会被设置。此外,AOP代理可能会用基于接口的代理来包装Bean实例,从而限制了对目标Bean实际类型的访问(仅暴露其实现的接口)。
要了解某个特定bean的实际运行时类型,推荐的方法是调用BeanFactory.getType并传入该bean的名称。这种方法会考虑上述所有情况,并返回BeanFactory.getBean在相同bean名称下将返回的对象类型。