目录
Spring IoC 容器用来管理一个或多个 bean。这些 bean 通过用户提供的配置文件创建(例如,xml 格式的
在容器中,bean 定义表示为 BeanDefinition 对象,BeanDefinition 对象包含以下元数据:
每个元数据都对应 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 进行读写
每个 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 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 定义进行冲突。
bean 定义本质上是一个用来创建一个或多个对象的菜单,当 Spring 容器需要使用 bean 定义创建或者获取实际的对象时,容器会通过查找菜单,获取特定名称的 bean 信息。
如果使用的是 xml 配置,那么需要在
嵌套类的类名
如果想要为嵌套类配置 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 容器对对象进行管理
- <bean id="serviceLocator" class="examples.DefaultServiceLocator">
-
- 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
确定一个 bean 的运行时类型并不容易。bean 配置中指定的类只是一个初始的类引用,该类可能与已经声明的工厂方法相结合,又或者该类是一个 FactoryBean 类型的类,也会导致 bean 的运行时类型可能不同,还有,在使用实例工厂方法的情况下,根本不设置 class 属性(通过指定的工厂 bean 名称解析)。此外,AOP 代理可以通过基于接口的代理包装 bean 实例,限制了目标bean 实际类型的公开(仅仅公开其实现的接口的类型)。
如果需要了解一个 bean 的实际运行时类型,推荐使用 BeanFactory.getType ,该方法在考虑了上述所有的情况下,可以通过指定 bean 名称获取对应的对象类型。// 不去通过 class 属性获取对象的类型,而是通过 name 属性