1.IoC容器概述
如果合作对象的引用或依赖关系的管理由具体的对象完成,会导致代码的高度耦合和可测试性的降低。在面向对象系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架或IoC容器来,这样做可以在解耦代码的同时提高代码的可测试性。
Spring中,IoC容器就是实现依赖控制反转(也称依赖注入)的载体,是一个JavaBean容器,它管理依赖关系。在具体的注入实现中,接口注入、setter注入、构造器注入是主要的注入方式。在Spring的IoC设计中,setter注入和构造器注入是主要的注入方式。
2.IoC容器的设计与实现:BeanFactory和ApplicationContext
2.1BeanFactory的设计原理
在Spring IoC容器的设计中,可以看到两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,这系列的容器只实现容器的最基本功能;另一个是ApplicationContext,它作为容器的高级形态而存在,它在简单容器的基础上,增加了很多面向框架的特性,同时对应用环境作了许多适配。

上图是IoC容器的接口设计图,可以看到所有接口的"源头"都是BeanFactory,该接口定义了IoC容器的基本规范,包含了
- getBean():从容器中取得Bean
- containsBean():判断容器是否含有指定名字的Bean
- isSingleton():查询指定名字的Bean是否是Singleton类型的Bean
- isPrototype():查询指定名字的Bean是否是prototype类型的Bean
- isTypeMatch():查询指定了名字的Bean的Class类型是否是特定的Class类型
- getType():查询指定名字的Bean的Class类型
BeanFactory定义了IoC容器最基本的方法,但我们在设计时经常将DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用,两者之间的关系可用下图来表示

我们熟知的很多IoC容器,都是通过持有或者扩展DefaultListableBeanFactory来获得基本的IoC容器的功能的。比如:XmlBeanFactory是一个可以读取以XML文件方式定义的BeanDefinition的IoC容器。在XmlBeanFactory中,初始化了一个XmlBeanDefinitionReader对象,对XML形式的信息的处理是由它来完成的。在构造XmlBeanFactory这个IoC容器时,需要指定BeanDefinition的信息来源,这个信息来源需要封装成Spring中的Resource类,完成此封装操作以后,可以通过XmlBeanDefinitionReader的loadBeanDefinitions()方法启动从Resource中载入BeanDefinitions的过程。
总的来看,要使用一个IoC容器,需要经历这样几个过程:
1) 创建IoC配置文件的抽象资源,这个抽象资源包含了BeanFactory的定义信息
ClassPathresource res = new ClassPathResource("beans.xml");2) 创建一个BeanFactory,这里使用DefaultListableBeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();3) 创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();4) 从定义好的资源位置读入配置信息,具体的解析过程由XmlDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IoC容器就建立起来了。这时候就可以使用IoC容器了
reader.loadBeanDefinitoins(res)Spring将上面的步骤分开,并使用不同的模块来完成。上面的第4步是IoC容器的初始化过程,后面将详细解析
2.2ApplicationContext的设计原理
我们以FileSystemXmlApplicationContext的实现说明ApplicationContext容器的设计原理。FileSystemXmlApplicationContext继承自AbstractXmlApplicationContext,ApplicationContext的主要功能已经在AbstractXmlApplicationContext中实现,而FileSystemXmlApplicationContext作为一个具体的应用上下文,实现了两个功能,一个功能是:如果应用直接使用FileSystemXmlApplicationContext,则可以实例化这个应用上下文,同时启动IoC容器的refresh()过程。这正是上面描述的使用IoC容器的过程:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh,
@Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}关于这个refresh的具体调用,后面详细解析
而FileSystemXmlApplicationContext的另一个功能就是为从文件系统中加载XML形式的BeanDefinition做准备,这个方法可以得到FileSystemXmlApplicationContext的资源定位
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}2.3IoC容器的初始化过程
IoC容器的初始化由前面的refresh()来启动,这个启动包括BeanDefinition的Resource定位、载入和注册三个基本过程。
Resource定位。它指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的Beandefinition的使用都提供了统一的接口。
BeanDifinition的载入。载入过程是把用户定义好的Bean表示成BeanDifinition,这个BeanDifinition实际上就是POJO对象在IoC容器中的抽象,通过这个BeanDifinition定义的数据结构,使IoC容器能够方便地对POJO对象也就是Bean进行管理
向IoC容器注册这些BeanDifinition。这个过程是通过调用BeanDefinitionRegisty接口的实现完成的。实际上,ioC容器内部将BeanDefinition注入到一个hashMap中,通过这个HashMap持有BeanDifinition数据
2.3.1BeanDefinition的Resource定位
Resource用来定位容器使用的BeanDefinition,比如
ClassPathResource res = new ClassPathResource("beans.xml");这里的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在ApplicationContext继承体系中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IoC容器。
不同的应用上下文提供不同的Resource读入功能。FileSystemXmlApplicationContext从文件系统载入Resource,ClassPathXmlApplicationContext从Class Path载入Resource,XmlWebApplicationContext在Web容器中载入Resource。下面以FileSystemXmlApplicationContext为例,分析Resource定位过程。FileSystemXmlApplicationContext的继承体系如下图:

从上图中我们可以看到,FileSystemXmlApplicationContext通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader。FileSystemXmlApplicationContext的具体实现如图:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
public FileSystemXmlApplicationContext() { }
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
//在对象的初始化过程中,通过refresh()载入BeanDefinition,refresh()启动了BeanDefinition的载入过程
public FileSystemXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
//这是应用于文件系统中Resource的实现,通过构造一个FileSystemResource来得到一个
//在文件系统中定位的BeanDefinition,这个方法在BeanDefinitionReader的loadBeanDefinition
//中被调用,loadBeanDefinition采用了模板模式,具体的实现又子类完成
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}在FileSystemXmlApplicationContext的构造函数中,实现了对configuration进行处理的功,让所有配置在文件系统中的,以XML文件方式存在的BeanDefinition都能够得到有效的处理。
根据下图,我们可以清楚地看到整个BeanDefinition资源定位的过程。这个过程由FileSystemXmlApplicationContext的构造函数中的refresh()触发,大致调用如下图所示:

refresh()继承自AbstractApplicationContext,其内部调用了obtainFreshBeanFactory(),而obtainFreshBeanFactory()调用了refreshBeanFactory(),它是抽象方法,在AbstractRefreshableApplicationContext中实现,在refreshBeanFactory()中通过createBeanFactory()创建了DefaultListableBeanFactory,这是前面提到了使用IoC容器的第二步;随后调用了AbstractXmlApplicationContext中实现的loadBeanDefinitions(),并在其中创建了XmlBeanDefinitionReader对象reader,这是第三步;最后通过reader.loadBeanDefinitions(Resource resource)完成第四步
下面看看XmlBeanDefinitionReader中loadBeanDefinitions如何工作的:
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
} return count;
}
}方法开始得到ResourceLoader对象,并调用getResourceByPath(),这个方法在FIleSystemXmlApplicationContext中已经实现,返回一个FileSystemResource对象,这里我们是以FileSystemXmlApplicationContext为例,如果使用的是其他ApplicationContext,则会返回不同的Resource对象。
至此,定位过程已经完成,后面一篇分析BeanDefinition的载入和解析