Bean概述

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

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

  • 一个包限定的类名:通常是定义的bean的实际实现类。

  • Bean行为配置元素,指定bean在容器中的行为方式(范围、生命周期回调等)。

  • 对于bean执行其工作所需的其他bean的引用。这些引用也称为协作者或依赖项。

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

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

表1. Bean定义
属性 解释在…​

实例化Bean

名称

命名Bean

范围

Bean范围

构造函数参数

依赖注入

属性

依赖注入

自动装配模式

自动装配协作者

延迟初始化模式

延迟初始化的Beans

初始化方法

初始化回调

销毁方法

销毁回调

除了包含有关如何创建特定bean的信息的bean定义之外,ApplicationContext实现还允许注册在容器外部(由用户)创建的现有对象。这是通过访问ApplicationContext的BeanFactory通过getBeanFactory()方法来完成的,该方法返回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提供nameid。如果您没有明确提供nameid,容器将为该bean生成一个唯一的名称。但是,如果您想通过ref元素或Service Locator样式查找来引用该bean,您必须提供一个名称。不提供名称的动机与使用内部bean自动装配协作者有关。

Bean命名约定

约定是在为bean命名时使用标准的Java约定,即bean名称以小写字母开头,然后采用驼峰命名法。这样的名称示例包括accountManageraccountServiceuserDaologinController等。

一致地命名bean使您的配置更易于阅读和理解。此外,如果您使用Spring AOP,在将建议应用于一组按名称相关的bean时会有很大帮助。

在类路径中使用组件扫描时,Spring为未命名组件生成bean名称,遵循前面描述的规则:基本上,取简单类名并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多于一个字符且第一个和第二个字符都是大写的,则保留原始大小写。这些规则与java.beans.Introspector.decapitalize定义的规则相同(Spring在此处使用)。

为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。

Java配置

如果您使用Java配置,可以使用@Bean注解提供别名。有关详细信息,请参阅使用@Bean注解

实例化Bean

Bean定义本质上是创建一个或多个对象的配方。当被询问时,容器会查看命名的bean的配方,并使用由该bean定义封装的配置元数据来创建(或获取)一个实际的对象。

如果您使用基于XML的配置元数据,您可以在<bean/>元素的class属性中指定要实例化的对象的类型(或类)。该class属性(在BeanDefinition实例中是一个Class属性)通常是必需的。(有关异常,请参见使用实例工厂方法进行实例化Bean定义继承。)您可以以以下两种方式之一使用Class属性:

  • 通常,为了指定容器自身通过调用其构造函数进行反射创建的bean的类,相当于使用Java代码中的new运算符。

  • 为了指定实际包含要调用以创建对象的static工厂方法的类,在容器调用类上的static工厂方法以创建bean的较不常见的情况。从调用static工厂方法返回的对象类型可以是相同的类,也可以是完全不同的类。

嵌套类名称

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

例如,如果您有一个名为SomeThing的类在com.example包中,并且这个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
			}
		}
					  
					

有关向工厂方法提供(可选)参数以及在从工厂返回对象后设置对象实例属性的机制的详细信息,请参阅详细的依赖关系和配置

使用实例工厂方法进行实例化

与通过静态工厂方法实例化类似,使用实例工厂方法通过容器中现有bean的非静态方法来创建新bean。要使用此机制,请将class属性留空,并在factory-bean属性中指定当前(或父级或祖先)容器中包含要调用以创建对象的实例方法的bean的名称。使用factory-method属性设置工厂方法本身的名称。以下示例显示了如何配置这样的bean:

<!-- 包含名为createClientServiceInstance()的方法的工厂bean -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
	<!-- 注入此定位器bean所需的任何依赖项 -->
</bean>

<!-- 通过工厂bean创建的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">
	<!-- 注入此定位器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的运行时类型

FactoryBean类,或者在实例级别工厂方法的情况下根本没有设置(通过指定的 factory-bean名称来解析)。此外,AOP代理可能会使用基于接口的代理包装bean实例,限制了目标bean的实际类型的暴露(仅暴露其实现的接口)。

BeanFactory.getType调用。这考虑了上述所有情况,并返回 BeanFactory.getBean调用将为相同bean名称返回的对象类型。