注1:Spring源码基于Spring3.1版本
注2:参考《Spring技术内幕》第二版
1、Spring IoC容器初始化
Spring IoC容器的初始化包括BeanDefinition的Resource定位、载入和注册三个基本过程。在具体分析这三个过程之前,需要注意的是Spring把这三个过程分开,并使用不同的模块来完成,这样使Spring IoC容器更加灵活。
Resource定位
这里Resource指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,对各种形式的BeanDefinition都提供了统一的接口。比如文件系统中的Bean定义信息可以使用FileSystemResource来抽象;类路径中的Bean定义信息可以使用ClassPathResource来抽象,等等。
这个Resource定位过程其实就是IoC容器寻找Bean定义数据的过程。
关于Spring中Resource的更多介绍,这里有一篇博客:http://blog.csdn.net/zhangyihui1986/article/details/8793626
BeanDefinition的载入
这个载入过程就是把定义好的Bean表示成容器内部的数据结构BeanDefinition。这个BeanDefinition实际上就是POJO对象在IoC容器中的抽象,通过这个BeanDefinition定义的数据结构,使IoC容器能够方便地对POJO对象也就是Bean进行管理。
向IoC容器注册BeanDefinition
这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,该注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。其实在IoC容器内部就是将BeanDefinition注入到一个HashMap中,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。
额外说明
需要注意的是,这里说的IoC容器的初始化过程一般不包括依赖注入的实现。在Spring的设计中,Bean定义的载入和依赖注入是两个独立的过程,依赖注入一般发生在应用第一次通过getBean向容器索取Bean实例的时候。有一个例外就是当我们在Bean定义中指定的lazyinit属性(预实例化配置),那么这个Bean的依赖注入在IoC容器的初始化时就完成了,不需要等到初始化完成之后,第一次获取该Bean时才触发。
下面从源码分析一下IoC容器初始化过程中的第一个步骤 -- Resource定位。
2、Resource定位
源码分析从FileSystemXmlApplicationContext类开始,因为《Spring技术内幕》中就是以该类为为起点来讲述IoC容器的初始化,这样可以引用书中的一些理论及代码解释。
如果我们以编程的方式使用DefaultListableBeanFactory时,会使用类似下面的方式:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Resource resource = new FileSystemResource("xxx.xml");
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
我们需要创建一个FileSystemResource(或其它Resource如ClassPathResource)来定位Resource,这里的Resource并不能为DefaultListableBeanFactory直接使用,而是通过BeanDefinitionReader(上例中使用实现类XmlBeanDefinitionReader)对这些信息进行处理。
而使用ApplicationContext相对于直接使用DefaultListableBeanFactory带来的一点好处就是ApplicationContext已经提供了一系列加载不同的Resource的功能,因为Spring中的各种ApplicationContext都实现了ResourceLoader接口(基类AbstractApplicationContext继承了DefaultResourceLoader,而DefaultResourceLoader是ResourceLoader接口的默认实现类)。
FileSystemXmlApplicationContext的继承体系
先分别从Spring源码角度和UML类图角度看一下FileSystemXmlApplicationContext的继承体系:
从继承体系可以看出,FileSystemXmlApplicationContext已经通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource形式定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader,而DefaultResourceLoader是ResourceLoader的默认实现。
FileSystemXmlApplicationContext源码实现:
package org.springframework.context.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
public FileSystemXmlApplicationContext() {}
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
// configLocation是BeanDefinition所在的文件路径
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
// 可以指定多个BeanDefinition资源路径
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
// 可以指定多个BeanDefinition资源路径, 同时指定自己的双亲容器
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
// 调用父类AbstractRefreshableConfigApplicationContext的方法,设置BeanDefinition定义的资源文件,完成IoC容器Bean定义资源的定位
// 调用父类AbstractApplicationContext的refresh()方法, 这个方法启动了BeanDefinition的载入过程, 是Ioc容器载入Bean定义的入口
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
// 覆盖父类DefaultResourceLoader的方法,通过FileSystemResource得到在文件系统中定位的BeanDefinition
// 该getResourceByPath方法在BeanDefinitionReader的loadBeanDefinition中被调用, loadBeanDefinition采用模版模式, 具体定位由子类完成
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
可以看到,在构造函数中实现了对configuration进行处理的操作,然后调用refresh方法启动IoC容器的初始化,这个refresh方法定义在AbstractApplicationContext类中,是一个非常重要的方法,是分析容器初始化过程的重要入口。
getResourceByPath的调用关系
下面我们来看一下refresh方法到getResourceByPath方法的调用关系图:
由上图可以清楚地看到整个BeanDefinition资源定位的过程,最初是由refresh方法触发,而refresh的调用是在FileSystemXmlApplicationContext的构造函数中启动。如果跟着源码去refresh方法中查看,就会发现refresh方法好像是一个总开关,在方法内部调用了很多其它的方法来完成IoC的初始化,其中就有obtainFreshBeanFactory方法,而obtainFreshBeanFactory又调用了AbstractRefreshableApplicationContext类中的refreshBeanFactory方法,正是由这个方法调用loadBeanDefinitions来加载BeanDefinition,下面来看一下refreshBeanFactory方法的实现。
Resource的定位源码
由上面分析可知,AbstractApplicationContext类的refresh方法是IoC容器初始化的入口,refresh方法调用了很多其它的方法,其实中就有obtainFreshBeanFactory方法,而obtainFreshBeanFactory又调用了AbstractRefreshableApplicationContext类中的refreshBeanFactory方法,我们从refreshBeanFactory方法开始分析。
AbstractRefreshableApplicationContext类中的refreshBeanFactory方法:
protected final void refreshBeanFactory() throws BeansException {
// 如果已经建立了BeanFactory,则销毁并关闭该BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建并持有DefaultListableBeanFactory实例,同时调用loadBeanDefinitions方法来载入BeanDefinition信息
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
// 创建DefaultListableBeanFactory实例,getInternalParentBeanFactory方法的实现可以参看AbstractApplicationContext类源码,它会根据已有的双亲IoC容器信息来生成DefaultListableBeanFactory的双亲容器
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
在这个方法中,通过调用createBeanFactory方法构建并持有一个IoC容器供ApplicationContext使用,这个IoC容器就是前面提到的DefaultListableBeanFactory,同时它启动了loadBeanDefinitions来载入BeanDefinition,这个过程与以编程式的方式来使用IoC容器(DefaultListableBeanFactory 结合 XmlBeanDefinitionReader)的过程非常相似。
接着进入到loadBeanDefinitions方法,这个方法在AbstractApplicationContext中是个抽象方法,具体的实现在AbstractXmlApplicationContext类中。
AbstractXmlApplicationContext的loadBeanDefinitions方法:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 使用刚创建的BeanFactory创建一个新的XmlBeanDefinitionReader.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 配置XmlBeanDefinitionReader.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化XmlBeanDefinitionReader, 然后加载BeanDefinition
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
// 实际加载BeanDefinition
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
从上面一段代码可以得知,loadBeanDefinitions方法首先创建了一个XmlBeanDefinitionReader实例,在完成配置与初始化之后执行了该XmlBeanDefinitionReader的loadBeanDefinitions方法,这里是真正载入BeanDefinition的地方,自此程序执行流程进入到XmlBeanDefinitionReader中。
跟着去看一下如何完成的。
XmlBeanDefinitionReader的loadBeanDefinitions方法:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 取得ResourceLoader, 这里使用的是DefaultResourceLoader, AbstractApplicationContext继承了DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 对Resource的路径模式进行解析, 得到需要的Resource集合, 这些Resource集合指向我们定义的BeanDefinition信息, 可以是多个文件
if (resourceLoader instanceof ResourcePatternResolver) {
// 调用DefaultResourceLoader的getResources方法完成具体的Resource定位
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
return loadCount;
} catch (IOException ex) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
}
} else {
// 调用DefaultResourceLoader的getResource方法完成具体的Resource定位.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
return loadCount;
}
}
由上面代码可以看出,多个资源定位符location会被逐一定位加载;另外,对于FileSystemXmlApplicationContext会进入loadBeanDefinitions方法中的else流程,这里会直接调用ResourceLoader的getResource方法,也就是其默认实现类DefaultResourceLoader的getResource方法。
DefaultResourceLoader的getResource方法:
// 具体取得Resource的过程, 定义在DefaultResourceLoader中
public Resource getResource(String location) {
// 处理带有classpath标识的Resource
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
// 作为URL定位的Resource处理
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException ex) {
// 不是URL标识的location, 将Resource定位任务交给getResourceByPath,
// 这是个protected方法, 默认实现为得到一个ClassPathContextResource, 常用来被子类重写.
return getResourceByPath(location);
}
}
}
在前面的源码分析中,getResourceByPath方法被DefaultResourceLoader的子类FileSystemXmlApplicationContext实现,这个方法返回一个FileSystemResource对象,通过该FileSystemResource对象,Spring便可以完成BeanDefinition定位。
分析到这里IoC容器FileSystemXmlApplicationContext的Resource的定位已经一目了然了,如果是其它类型的ApplicationContext,那么会相应生成其它各类的Resource,比如ClassPathResource、ServletContextResource等。