Bean 概述
Spring IoC 容器管理一个或多个 bean,这些 bean 是使用你提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)。
在容器内部,这些 bean 定义被表示为 BeanDefinition
对象,其中包含(其他信息)以下元数据:
- 包限定的类名:通常,定义 bean 的实际实现类。
- Bean 行为配置元素,它声明 Bean 在容器中的行为(作用域、生命周期回调,等等)。
- 对 bean 执行其工作所需的其他 bean 的引用,这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置 — 例如,池的大小限制或在管理连接池的 bean 中使用的连接数。
此元数据转换为组成每个 bean 定义的一组属性,下表描述了这些属性:
属性 | 解释 |
---|---|
Class | 实例化 Bean |
Name | 命名 Bean |
Scope | Bean 作用域 |
构造函数参数 | 依赖注入 |
属性 | 依赖注入 |
自动装配模式 | 自动装配协作者 |
延迟初始化模式 | 延迟初始化 Bean |
初始化方法 | 初始化回调 |
销毁方法 | 销毁回调 |
除了包含关于如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext
实现还允许注册(由用户)在容器外部创建的现有对象,通过 getBeanFactory()
方法访问 ApplicationContext
的BeanFactory
,该方法返回 BeanFactory DefaultListableBeanFactory
实现。DefaultListableBeanFactory
通过 registerSingleton(..)
和registerBeanDefinition(..)
方法支持这种注册,然而,典型的应用程序只使用通过常规 bean 定义元数据定义的 bean。
Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确地推断它们,虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但是在运行时注册新 bean(与对工厂的实时访问同时进行)并不是官方支持的,并且可能会导致并发访问异常、bean 容器中不一致的状态,或者两者兼有。
命名 Bean
每个 bean 都有一个或多个标识符,这些标识符在承载 bean 的容器中必须是惟一的,bean 通常只有一个标识符,但是,如果需要多个,则可以将额外的标识符视为别名。
在基于 XML 的配置元数据中,可以使用 id
属性、name
属性或两者都使用来指定 bean 标识符,id
属性允许你精确地指定一个 id,通常,这些名称是字母数字(’myBean‘、’someService‘ 等),但它们也可以包含特殊字符。如果希望为 bean 引入其他别名,还可以在name
属性中指定它们,以逗号 (,
)、分号(;
) 或空格分隔。作为历史记录,在 Spring 3.1 之前的版本中,id
属性被定义为 xsd:ID
类型,这限制了可能的字符,从 3.1 开始,它被定义为 xsd:string
类型,注意,bean id
惟一性仍然由容器执行,但不再由 XML 解析器执行。
你不需要为 bean 提供 name
或id
,如果不显式提供 name
或id
,容器将为该 bean 生成唯一的名称,但是,如果你希望通过名称引用该 bean,通过使用 ref
元素或服务定位器样式查找,你必须提供一个名称,不提供名称的动机与使用内部 bean 和自动装配协作者有关。
Bean 命名约定
约定是在为 bean 命名时使用标准 Java 约定作为实例字段名,也就是说,bean 名称以小写字母开头,并采用驼峰大小写,此类名称的示例包括
accountManager
、accountService
、userDao
、loginController
等等。一致地命名 bean 使你的配置更容易阅读和理解,另外,如果你使用 Spring AOP,当你将advice 应用到一组按名称关联的 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
的名称引用数据源,子系统 B 的配置元数据可以通过 subsystemB-dataSource
的名称引用数据源。在组合使用这两个子系统的主应用程序时,主应用程序通过 myApp-dataSource
的名称引用数据源,要使所有三个名称都引用同一个对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过惟一的名称引用数据源,并且保证不会与任何其他定义冲突(有效地创建名称空间),但是它们引用的是同一个 bean。
Java 配置
如果使用 Java 配置,可以使用
@Bean
注解提供别名,有关详细信息,请参见使用@Bean
注解。
实例化 Bean
bean 定义本质上是创建一个或多个对象的配方,当被询问时,容器会查看命名 bean 的配方,并使用该 bean 定义封装的配置元数据来创建(或获取)一个实际对象。
如果使用基于 XML 的配置元数据,则要在 <bean/>
元素的 class
属性中指定实例化的对象的类型(或类),这个 class
属性(在内部是 BeanDefinition
实例上的一个 Class
属性)通常是强制性的,你可以通过以下两种方式之一使用 Class
属性:
- 通常,在容器本身通过反射调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,这有点类似于使用
new
操作符的 Java 代码。 - 要指定包含创建对象时调用的
static
工厂方法的实际类,在不太常见的情况下,容器调用类上的static
工厂方法来创建 bean,从static
工厂方法调用返回的对象类型可以是同一个类,也可以完全是另一个类。
内部类的名字
如果要为
static
嵌套类配置 bean 定义,必须使用嵌套类的二进制名称。例如,如果你在
com.example
包中有一个名为SomeThing
类,这个类有一个static
嵌套类叫做OtherThing
,bean 定义上的class
属性的值是com.example.SomeThing$OtherThing
。注意,在名称中使用
$
字符将嵌套的类名与外部类名分隔开。
使用构造函数实例化
当你使用构造函数方法创建 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()
方法必须是一个静态方法,下面的例子展示了如何指定工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
下面的例子展示了一个使用前面 bean 定义的类:
public class ClientService {private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {return clientService;}
}
有关向工厂方法提供(可选)参数并在对象从工厂返回后设置对象实例属性的机制的详细信息,请参阅依赖项和配置。
使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法实例化将从容器中调用现有 bean 的非静态方法来创建新 bean,要使用此机制,请保留 class
属性为空,并在 factory-bean
属性中,在当前(或父或祖先)容器中指定 bean 的名称,该容器包含要调用来创建对象的实例方法,使用 factory-method
属性设置工厂方法本身的名称,下面的例子展示了如何配置这样一个 bean:
<!-- the factory bean, which contains a method called createInstance() -->
<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 类:
public class DefaultServiceLocator {private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {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 类:
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;}
}
这种方法表明,工厂 bean 本身可以通过依赖项注入(DI)进行管理和配置。
在 Spring 文档中,“工厂 bean”指的是在 Spring 容器中配置的 bean,它通过实例或静态工厂方法创建对象,相反,
FactoryBean
(注意大小写)指的是特定于 spring 的FactoryBean
。