Spring源码阅读之IoC容器初始化1 -- Resource定位

11 篇文章 0 订阅

注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等。


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值