Spring IoC 容器与 Bean 简介

It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. - Spring Documentation

IoC 也被称为 DI,它表示了这样一种过程:对象通过且仅通过构造器参数/将参数传递给工厂方法来定义/定位其所需依赖,或者手动实例化对象/从工厂获得实例之后再设置所需属性。

org.springframework.beansorg.springframework.context 包是 Spring 框架中 IoC 容器的基础,BeanFactory 接口提供了一种可以管理任意类型的对象的配置机制,ApplicationContextBeanFactory 的一个子接口,它添加了以下一些特性:

  • 简便地与 Spring AOP 的集成
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层面的特定上下文,如 Web 应用中的 WebApplicationContext

简而言之,BeanFactory 提供了配置框架和基本功能,ApplicationContext 添加了一些企业级的功能。ApplicationContextBeanFactory 的完全超集并不仅被用于在本章描述 Spring IoC 容器,如需了解 BeanFactory 而非 ApplicationContet 的更多使用,可以参考 BeanFactory API

在 Spring 中,组成应用的轴心骨 - 被 Spring IoC 容器所管理的对象被称为 Bean,Bean 的含义是“被 Spring IoC 容器初始化、组装、管理的对象”,Bean 也是应用程序中的众多对象之一。Bean 以及 Bean 所需的依赖,被容器作为配置元数据反射调用。

容器概览

org.springframework.context.ApplicationContext 接口代表着 Spring 的 IoC 容器,并且负责初始化/实例化、配置、组装 Bean,IoC 通过读取配置元数据来决定初始化/配置/组装什么对象,配置元数据通常以XML、Java 注解、或 Java 代码的方式出现,配置元数据使开发者可以表达对应用所需对象的组合方式以及对象之间的内联依赖/关系。

Spring 提供了众多 ApplicationContext 接口的实现,在单机应用中,通常是创建 ClassPathXmlApplicationContextFileSystemXmlApplicationContext。尽管 XML 时传统的定义配置元数据的方式,开发者也可以指示容器使用 Java 注解或代码作为配置元数据,但是也需要使用 XML 进行一些简单的配置以表明需要支持这些扩展的数据格式。

在大多数的应用场景中,并不需要显式地用户代码来初始化一个/多个 Spring IoC 容器的实例,如在 Web 应用中,通过一个 web.xml 就可以声明一个 Spring IoC 容器。

Spring IoC 容器的基本工作方式:

  • 输入:业务对象(POJOs)+ 配置元数据(Configuration Metadata)
  • 处理:Spring IoC 容器 ApplicationContext 处理输入数据
  • 输出:开箱即用/完全配置的应用系统

Figure 1. The Spring IoC container

配置元数据

如前文/上图所述,Spring IoC 容器接受某种类型的配置元数据,这些元数据指示 Spring 容器初始化/配置/组装应用对象。

配置元数据通常为 XML 格式,也是 Spring 官方文档用来讲解 Spring IoC 容器关键特性与概念的数据格式。

Note: Spring IoC 容器与 XML 是完全解耦合的,目前 Java 配置才是主流方式。

关于如何使用 XML 格式之外的其他数据格式来配置元数据,可以参考:

Spring 配置包含至少一个(通常超过一个)容器必须管理的 Bean,XML 配置元数据使用 <bean/> 配置 Bean,并内嵌在 <beans/> 之内,Java 代码则使用 @Bean 注解标注在被 @Configuration 注解标注的 Class 中的方法上。

Bean 的定义与组成应用的真是对象是互相对应的,通常,开发者定义服务层对象,持久层对象 repositories 或 DAOs,展示层对象 Web Controller,底层对象如 JPA EntityManageFactory,JMS 队列等。通常来说,并不需要在容器中定义精心雕琢过的领域对象,因为创建和加载领域对象是 repositories 和业务逻辑的责任。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
<!-- id 可被合作对象引用 -->
<property name="..." ref="...">
<property name="..." ref="...">
</bean>

<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions go here -->

</beans>

在这面这个例子中,property name 指向 JavaBean 属性,ref 指向另一个 Bean 的定义,idref 之间的联系表现了协作对象之间的联系。

初始化容器

传递给 ApplicationContext 构造器的路径会引导容器从外部资源加载配置元数据,如本地文件系统、Java 类路径等。

1
ApplicationContext context = new ClassPathXmlApplication("service.xml", "daos.xml);

Note: 在了解了 Spring IoC 容器之后,也许还需要了解 Spring 的抽象资源 ResourceResource 提供了便捷的从路径中读取以 URI 语法定义的输入流机制。特别的,Resource 路径被用于构造容器的上下文,详情可参考 Application Contexts and Resource Paths

编写基于 XML 配置元数据

将 Bean 的定义分散在多个不同的 XML 在某些场景下是非常有用的,每一个独立的 XML 配置文件代表了应用架构中的某一个逻辑层。

可以使用应用上下文构造器加载多个不同 XML 片段中的 Bean 定义,这个构造器接受一个或多个 Resource 路径,也可以在 XML 文件中使用 <import/> 标签加载其他文件中的 Bean 定义,以下是一个例子。

1
2
3
4
5
6
7
8
9
<beans>
<import resource="services.xml"/>
<!-- 虽然也支持 classpath 与绝对路径,但尽量不要这么做。-->
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>

<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>

使用 IoC 容器

ApplicationContext 是管理不同 Bean 注册及其依赖的高级接口,通过 T getBean(String name, Class<T> requiredType) 方法可以获取 Bean 的实例。

1
2
3
4
5
6
7
8
// 创建并配置 Bean
ApplicationContext context = new ClassPathXmlApplication("services.xml", "daos.xml");

// 获取配置好的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用配置好的实例
List<String> userList = service.getUsernameList();

ApplicationContext 接口也有一些除 getBean(...) 之外的获取 Bean 实例的方法,但是在应用程序中,一般来说,除非有对 Spring API 的直接调用,否则不应该出现对 getBean 的显式使用。

Bean 简介

Spring IoC 容器管理一个或多个 Bean,这些 Bean 通过通过配置元数据被加载到容器之中。在容器内部,这些 Bean 的定义由 BeanDefinition 对象表示,包含以下一些属性:

  • 包限定类名:通常是某个 Bean 的具体实现类
  • Bean 的行为配置元素,表明了 Bean 应该在 Container 的状态与行为,如作用域、生命周期回调等。
  • 完成当前 Bean 所需功能而对其他 Bean 的引用,这些引用也被称为依赖/合作对象。
  • 其他需要为新建对象设置的属性,如一个管理连接池的 Bean 所支持的连接数量以及连接池的大小。

上面这些元数据表明了组成每个 Bean 定义的基本属性,下表则描述了这些属性:

属性 链接
Class Bean 的初始化
Name Bean 的命名
Scope Bean 的作用域
Constructor arguments 依赖注入
Properties 依赖注入
Autowiring mode 自动装配协作器
Lazy initialization mode Bean 的懒初始化
Initialization method 初始化回调
Destruction method 销毁回调

除去创建某个包含特定 Bean 定义的 Bean 之外,ApplicationContext 的实现也允许某些用户自建对象注册到容器之中,可以通过BeanFactorygetBeanFactory() 方法实现,这个方法返回了 DefaultListableBeanFactoryDefaultListableBeanFactory 支持通过 registerSingleton(..)registerBeanDefinition(..) 方法注册 Bean。

Bean 的元数据与手动注册的 Bean 实例需要尽早注册,以便于容器在自动装配及解析时可以正确使用。尽管容器在某种情况下支持重写已存在的元数据和单例实例,但这并不包括在运行时注册 Bean,这可能会导致并发访问,Bean 数据不一致性等问题。

Bean 的命名

每个 Bean 在容器内可以拥有一个或多个具备唯一性的标识符,但通常来说每个 Bean 只有一个标识符。然而,如果需要有超过一个的标识符,这些标识符可以以“别名(alias)”的形式存在。

在基于 XML 的配置元数据中,使用 idname 属性作为 Bean 标识符,如果 name 属性有多个值存在时,可以使用逗号 ,,分号 ; 或空格分隔。

Beanidname 属性并不是必须提供的,如果缺省,容器会为 Bean 创建一个唯一的名字,然而,如果需要使用 ref 属性应用一个 Bean,那就必须提供 name

关于为什么可以不提供 name,可以参考 inner beansautowiring collaborators

Bean 一般使用驼峰命名法,并有利于 Spring AOP 应用 Advice

Note

在扫描类路径中的组件时,Spring 会为未未名的组件生成 Bean 名,规则是:将类的 simpleName 的首字母转为小写。

然而,在一些特殊的场景中,如包含不止一个字符的命名中,首字母和第二个字符均为大写时,则会保留原始的命名,此规则定义在 java.beans.Introspector.decapitalize

为 Bean 起别名的例子:

1
2
3
4
5
6
<!-- fromName 与 toName 均指向同一个 Bean -->
<alias name="fromName" alias="toName"/>

<!-- 以下两个语句中的三个不同的名字,都指向同一个 Bean -->
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

另外,也可以使用 Java 代码在 @Bean 注解中添加 alias 属性,可参考 Using the @Bean Annotation

初始化 Bean

Bean 的定义本质上是用来创建一个或多个对象的清单/目录/指示,当容器被要求使用某个配置元数据时,容器就在这个清单中查找某个名字的 Bean 定义,并创建出实际的对象。

在基于 XML 的配置元数据中,通过 <bean/> 元素的 class 属性指定要初始化的对象类型通常是必须的(实际上是 BeanDefinition 实例的 Class 属性),但也有例外,可参考 使用实例工厂方法进行实例化Bean 定义与继承Class 属性有以下两种使用方式:

  • 通常,容器指定要构建的 Bean 的类型,直接通过反射调用 Bean 的构造器直接创建 Bean,等价于 Java 代码的 new 操作符。
  • 在包含用于创建对象的静态工厂方法的实际类中,容器直接调用类中的静态工厂方法创建 Bean 这种操作并不常见,通过静态工厂方法返回的对象的类型可能属于同一类型,但也可能完全不同。

内部类名称

如果需要为内部类配置 Bean 定义,要么使用二进制名,要么使用嵌套类的实际名。

例如,假设在 com.example 包下存在一个名为 SomeThing 的类,而且 SomeThing 类有一个静态内部类 OtherThing,可以使用 $. 来分割外部类与内部类。所以 Bean 定义中的 class 属性为 com.example.SomeThing$OtherThingcom.example.SomeThing.OtherThing

使用构造器实例化

当使用构造器创建 Bean 时,所有的普通类对 Spring 来说都是可用的并且可兼容的,简单来说就是开发出的类并不需要实现任何接口或者按照特定的风格编码,只需要指定 Bean 的类即可,然而对于某个 Bean 而言,可能需要根据 IoC 的类型指定一个无参构造器。

Spring IoC 容器可以虚拟地管理你想要管理的任何类,并不仅限于 JavaBean,但是大多数的 Spring 用户偏向于只拥有一个无参构造器以及若干 getters/setters 的 JavaBean。

更多关于构造器初始化的内容,可参考 Injecting Dependenies

使用静态工厂方法实例化

使用实例工厂方法实例化

确定 Bean 的运行时类型

评论