目录
Spring IoC 容器用来管理一个或多个 bean。这些 bean 通过用户提供的配置文件创建(例如,xml 格式的 <bean/> 定义等)。
在容器中,bean 定义表示为 BeanDefinition 对象,BeanDefinition 对象包含以下元数据:
- 包限定(完整包路径)类名:通常是定义的 bean 的实际实现类。// 类名
- Bean 的行为配置,声明 Bean 在容器中的行为方式(如范围、生命周期回调等等)。
- 对 bean 完成其工作所需的其他 bean 的引用。这些引用也称为协作或依赖关系。// 依赖
- 对象创建的其他配置。例如,对连接池的大小限定或者配置连接池 bean 中使用的连接数等。// 属性
每个元数据都对应 bean 定义的一组属性。下表中描述了这些属性:
属性 | 说明 |
Class | 类(实例化 Bean ) |
Name | Bean 命名 |
Scope | Bean 范围(singleton/prototype...) |
Constructor arguments | 构造器参数 |
Properties | Bean 属性 |
Autowiring mode | 自动注入模型(by Name、by Type) |
Lazy initialization mode | 懒加载模型( lazy-init="true" ) |
Initialization method | 初始化方法 |
Destruction method | 销毁方法 |
除了加载通过 Bean 定义创建的特定 Bean ,Spring 容器还允许加载在容器外部创建的对象。通过 getBeanFactory() 方法访问 ApplicationContext 的 BeanFactory ,该方法会返回DefaultListableBeanFactory 的实现。DefaultListableBeanFactory 通过 registerSingleton(..) 和 registerBeanDefinition(..) 方法支持外部创建对象的注册。但是,一般应用程序仅使用通过常规 bean 定义创建的 Bean。// 容器通过Bean定义创建Bean,也可以加载外部创建的 Bean
Bean 元数据和手动提供的单实例需要尽早注册,容器才能在自动装配和其他步骤中正确地使用这些 Bean。虽然 Spring 支持覆盖现有元数据和现有单实例的操作,但并不支持在运行时注册新的 bean(创建和访问 BeanFactory 不能同时进行),因为这可能会导致并发访问异常,或者 bean 容器中的状态不一致问题。// 不能同时对 BeanFactory 进行读写
1、如何命名 Beans
每个 bean 都有一个或多个标识符。这些标识符在 bean 容器中必须是唯一的。通常,一个 bean 只有一个标识符。但是,如果 bean 需要定义多个标识符,那么额外的标识符就需要使用别名。// bean 具备唯一标识
在基于 xml 的配置文件中,可以使用 id 属性、name 属性或两者都使用来指定 bean 的标识符。id 属性可以让你精确的指定一个 id 。按照惯例,name 属性的值都是字母或数字(如 'myBean'、'someService' 等),但是它也可以包含一些特殊字符。如果希望为 bean 引入其他的别名,也可以在 name 属性中指定它们,用逗号(,)、分号(;)或空格进行分隔。在 Spring 3.1 之前的版本中,id 属性被定义为 xsd: id 类型,它限制了可使用的字符。从 3.1 开始,它被定义为 xsd:string 类型。但是,bean id 的惟一性仍然由容器强制执行,但不再通过 XML 解析器强制执行。// bean 的命名规则
为 bean 提供名称或 id 不是必须的。如果不显式提供名称或 id,容器将为该 bean 生成惟一的名称。但是,如果想通过名称引用该 bean,通过 ref 元素或 Service Locator 方式对 bean 进行查找,那么必须提供 bean 的名称。不提供 bean 名称的机制与使用内部 bean 和自动装配有关。// 为 bean 提供名称或 id 不是必须的?有尝试过这种情况吗?匿名内部类?
在 bean 定义之外使用别名命名 bean
在 bean 定义中,通过 id 属性和任何数量的 name 属性组合可以为 bean 提供多个名称。这些名称可以作为同一个 bean 的别名,它们在某些情况下很有用。例如,不同的应用程序可以使用不同的别名去引用同一个公共的 bean 。
当定义 bean 时,并不一定能一次性指定所有别名,有时候,在其他地方也需要定义 bean 的别名。大型系统中,在不同文件中定义别名是常见的情况,其中配置会被划分到每个子系统中,而每个子系统都有自己的 bean definitions 配置。在 xml 配置中,可以使用 <alias/> 标签来实现这种操作。下面如何做到这一点的例子展示:
<alias name="fromName" alias="toName"/>
在这种情况下,同一个容器中命名为 fromName 的 bean ,除了可以使用 fromName 进行引用,也可以使用 toName 进行引用。
例如,子系统 A 的配置可以通过 subsystemA-dataSource 的名称引用数据源。子系统 B 的配置可以通过 subsystemB-dataSource 的名称引用数据源。在组成使用这两个子系统的主应用程序时,主应用程序通过 myApp-dataSource 的名称引用数据源。如果要使三个名称都引用同一个对象,可以在配置中添加以下别名的定义:
// 数据源 A
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
// 数据源 B
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,主应用程序和每个组件都可以通过唯一的名称引用 dataSource,且引用的都是同一个 bean,并且不会与其他的 Bean 定义进行冲突。
2、如何实例化 Beans
bean 定义本质上是一个用来创建一个或多个对象的菜单,当 Spring 容器需要使用 bean 定义创建或者获取实际的对象时,容器会通过查找菜单,获取特定名称的 bean 信息。
如果使用的是 xml 配置,那么需要在 <bean/> 标签的 class 属性中指定需要实例化的对象的类型(或类)。这个 class 属性通常是必须配置的(在内部是 BeanDefinition 实例上的 class 属性)。可以通过以下两种方式使用 class 属性:// 配置class 属性
- 指定有构造方法的 bean 类。容器通过反射性调用构造函数可以直接创建 bean 的实例,这种情况与 Java 中的 new 操作有些相同。// 使用构造函数
- 指定有静态工厂方法(用来创建对象)的 bean 类。在实际情况中,Spring 容器很少通过类中的静态工厂方法去创建一个实例。因为,通过调用静态工厂方法返回的对象类型可能是同一个类,也可能是另一个完全不同的类。// 使用静态工厂
嵌套类的类名
如果想要为嵌套类配置 bean 定义,可以使用嵌套类的二进制名称或源名称。
例如,如果在 com.example 包中有一个名为 SomeThing 的类。在 SomeThing 类中有一个静态嵌套的类 OtherThing ,它们之间可以用美元符号 ($) 或点 (.) 来进行分隔。所以,嵌套类在 bean 定义中 class 属性的值应该是 com.example.SomeThing$OtherThing 或者 com.example.SomeThing.OtherThing。// 嵌套类/内部类
(1)使用构造方法进行实例化
当通过构造函数方法创建 bean 实例时,Spring 可以使用并兼容所有的普通类。也就是说,正在开发中的类不需要实现任何特定的接口,也不需要以特定的方式编码,只需指定 bean 的类就足够了。不过,这也取决于 Bean 注入时使用的 IOC 类型,有些情况下会需要提供一个默认(空)的构造函数。// 提供默认的构造函数
Spring IoC 容器可以管理任何类,并不仅仅局限于管理 JavaBeans。大多数 Spring 用户更喜欢使用 JavaBeans,它只有一个默认的(无参数的)构造函数以及一些根据属性创建的 setter 和 getter 方法。此外,你也可以在容器中定义一些非 Bean 样式的类。比如,使用一个完全不符合 JavaBean 规范的遗留连接池(a legacy connection pool ),Spring 同样也能很好的管理它。// 管理非 Bean 样式的类,从来没有用过?a legacy connection pool 是啥?
使用 xml 的配置,可以按照以下方式指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
如果想要详细了解如何向构造函数提供参数,以及在对象构造后,如何为对象实例设置属性,可以去查看依赖注入相关的资料。
(2)使用静态工厂方法进行实例化
当定义的类中有创建 bean 实例的静态工厂方法时,除了需要指定 class 属性,还需要通过 factory-method 属性去指定工厂方法的名称。调用这个工厂方法时,会返回可用的实例对象,该对象实际上也是通过构造函数创建的。这种定义 bean 的方式,被称为静态工厂模式。// 静态工厂模式
下面的 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;
}
}
(3)使用实例工厂方法进行实例化
实例工厂方法与静态工厂方法方式有些类似。不同的是,实例工厂方法是调用类中的非静态方法来实例化 bean 。要使用这种方式,属性 class 需要保留为空,在属性 factory-bean 中,需要指定容器中具备创建对象实例方法的 bean 的名称,同时,属性 factory-method 需要指定工厂方法的名称,下面的例子展示了如何配置这样一个 bean:// 通过一个类的普通方法去创建另一个对象,并使用 Spring 容器对对象进行管理
<!-- 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"/>
下面的例子展示了对应的类:
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"/>
下面的例子展示了对应的类:
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;
}
}
注意:在 Spring 文档中, "factory bean" 指的是在 Spring 容器中配置的 bean,它通过实例或者静态工厂方法创建。相比之下,FactoryBean(注意大小写)是 Spring 中特定的接口 FactoryBean 的一个实现类。// FactoryBean 和 "factory bean" 的区别,FactoryBean 也是一个特殊的 Bean
3、确定 Bean 的运行时类型
确定一个 bean 的运行时类型并不容易。bean 配置中指定的类只是一个初始的类引用,该类可能与已经声明的工厂方法相结合,又或者该类是一个 FactoryBean 类型的类,也会导致 bean 的运行时类型可能不同,还有,在使用实例工厂方法的情况下,根本不设置 class 属性(通过指定的工厂 bean 名称解析)。此外,AOP 代理可以通过基于接口的代理包装 bean 实例,限制了目标bean 实际类型的公开(仅仅公开其实现的接口的类型)。
如果需要了解一个 bean 的实际运行时类型,推荐使用 BeanFactory.getType ,该方法在考虑了上述所有的情况下,可以通过指定 bean 名称获取对应的对象类型。// 不去通过 class 属性获取对象的类型,而是通过 name 属性