Spring 源码分析--IOC的详细过程

关于Spring,我们先从Spring的架构图开始说起
Spring 源码分析--IOC的详细过程_第1张图片
这是一张经典图,介绍了Spring 各个模块的位置和作用,SpringIOC是最基本的模块,包含了最为基本的IOC容器BeanFactory,也包括一系列它的实现,当然单纯的容器是不能够支撑开发的,还需要一系列外围支持,就像只有CPU,没有外围一样,这些外围包括支持Resource访问资源的抽象和定位。另外,Spring还提供了IOC容器的高级形态ApplicationContext应用上下文供用户使用,刚学Spring的时候,我们应该都用过ClassPathXmlApplicationContext这个类,也许,真像离我们真的很近。
IOC和控制反转
如果对象的引用或者依赖关系由具体对象来完成。会导致代码的高度耦合,而且有一种牵一发而动全身的感觉,这种复杂的面向对象系统的设计是非常不利的;如果,将这些依赖关系交给框架或者IOC容器来完成,那么就可以在解耦代码的同时提高代码的可测试性。在Spring中,IOC容器是这个模式的载体,IOC也是Spring框架要解决的核心问题。下面我们从IOC中两个核心的接口BeanFactory和Appliationontext中来秘境探幽。
我们知道,BeanFactory接口是一个根接口,他有一系列实现,这部分实现了容器的基本功能;另一个是ApplicationContext应用上下文,它作为容器的高级形态出现,增加了许多面向框架的特性。另外一点,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及他们之间的相互依赖关系。IOC和BeanDefinition的关系是这样的,IOC是用来管理对象依赖关系的,而BeanDefinition是让容器起作用的数据模型,是对控制反转模式中管理的对象依赖关系的数据抽象,控制反转是通过围绕其来完成的,就像水桶和水的关系,IOC是水桶,BeanDefinition是水。

我们知道BeanFactory定义了几个关于Bean的最基本方法,他们是:

  • containsBean,判断容器中是否有指定名字的Bean
  • isSingleton,查询指定名字的bean是否是Singleton类型的Bean
  • isTypeMatch,查询指定了名字的Bean的Class类型是否是特定的Class类型
  • getType,查询指定名字的Bean的Class类型。
    其具体代码入如下:
    Spring 源码分析--IOC的详细过程_第2张图片
    在BeanFactory的众多实现中,有一个比较重要的实现类DefaultListableBeanFactory,很多常用的容器,如AppliationContext、XmlBeanFactory等都是继承自该类来得到基本的IOC容器的功能的。下面以编程的方式使用下DefaultListableBeanFactory,以便了解IOC容器的一些基本过程。简单的四行代码如下。
    //1、创建IOC配置文件的抽象资源,包含BeanDefinition的定义信息
    ClassPathResource resource = new ClassPathResource("META-INF/spring/minstrel.xml");
    //2、创建一个BeanFactory
    DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
    //3、创建一个载入BeanDefinition的读取器,其通过一个回调配置给BeanFactory
    XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
    //4、从定义好的资源位置读入配置信息,完成整个载入和注册Bean的功能
    xmlBeanDefinitionReader.loadBeanDefinitions(resource);

上面介绍了一部分理论知识,其实只是很小的一部分,因为我们的重点是分析源码,最好能一步步断点调试下。所以下面以ClassPathXmlApplicationContext入手,来分析下。
简单来说,IOC容器的初始化是由下面的refresh()方法来启动的,整个启动包括BeanDefinition的Resource定位、载入和注册三个过程。

	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

下面简单先叙述介绍下这三个过程:

  • Resource定位过程,即BeanDefinition的资源定位,它是由ResourceLoader通过统一的Resource接口来完成的,这个Resource对各种形式的BeanDefinition都使用了统一接口。如FileSystemResource、ClassPathResource等,其实这就是容器寻找数据的过程
  • BeanDefiniton的载入。即把Bean表示成IOC容器识别的数据结构的过程,即BeanDefinitoon
  • 向IOC容器注册上述的BeanDefinition,这是通过调用BeanDefinitionRegistry完成的,最终我们看到是通过HashMap来持有BeanDefinition的。
    下面具体看下每个过程
    Resource的定位
    以ClassPathXmlApplicationContext为例,我们知道其是从ClassPath中载入Resource,通过继承AbstractApplicationContext,从而具备了ResourceLoader读入Resource定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader。
    下面进入源码,看看,首先是ClassPathXmlApplicationContext。
    Spring 源码分析--IOC的详细过程_第3张图片
    Spring 源码分析--IOC的详细过程_第4张图片
    重点提一下第二个构造函数,在构造函数中有一个refresh()方法,这是启动IOC容器的初始化,这是一个非常重要的方法。整个资源定位的大致过程如下:
    Spring 源码分析--IOC的详细过程_第5张图片
    其实从这里并没有看到,BeanDefinition的读入器BeanDefinitionReader,关于这读入器的配置,看下其基类AbstractRefreshableConfigApplicationContext中refreshBeanFctory()的实现。在refresh()的时候会去获取BeanFactory,获取BeanFactory时会调用refreshBeanFctory()。其具体实现如下:
    Spring 源码分析--IOC的详细过程_第6张图片
    里面关键的信息在获取DefaultListableBeanFactory和loadBeanDefinitions()方法中。
    Spring 源码分析--IOC的详细过程_第7张图片
    上述代码直接声明了一个ResourceReader,在上述代码框出来的地方设置ResourceLoader,然后loadBeanDefinition()加载资源。资源定位大概就是这样,相当于我们的水桶找到水了,那么怎么打水咩,下面看下BeanDefinition的载入和解析
    BeanDefinition的载入和解析
    IOC容器对Bean的管理和依赖注入功能是通过对其持有的BeanDefinition进行各种操作完成的,这些BeanDefinitions数据在IOC容器中通过一个HashMap来保持和维护。下面还是从DefaultListableBeanFactory入手,来看看IOC载入BeanDefinition的过程。前面介绍了,IOC容器初始化的入口是在refresh方法中,它的调用标志着容器初始化的开始,这些初始化对象就是BeanDefinition数据。该方法详细描述了初始化过程,比如BeanFactory的更新,MessageSource的注册等,这个执行过程为Bean的生命周期管理提供了条件。具体看下:
    Spring 源码分析--IOC的详细过程_第8张图片
    具体的交互过程如下:
    Spring 源码分析--IOC的详细过程_第9张图片
    从前面的介绍中我们知道,在refreshBeanFactory方法中会调用loadBeanDefinitions()以启动对BeanDefinition的载入,我们前面也看到了在loadBeanDefinitions方法中初始化了读取器BeanDefinitionRedader,然后把这个读取器在IOC容器中设置好,最后是启动读取器来完成BeanDefinition在IOC容器中的载入。loadBeanDefinitions方法如下:
    Spring 源码分析--IOC的详细过程_第10张图片
    首先得到BeanDefinition信息的Resource定位,然后调用Reader来读取
    Spring 源码分析--IOC的详细过程_第11张图片
    在读取器中,需要得到代表XML文件的Resource,因为这个Resource对象封装了对XML文件的IO操作,所以读取器可以在打开IO流后得到XML的文件对象,有了这个文件对象之后,就可以按照Spring的Bean定义规则来对XML文档树进行解析了,解析工作是在前面图中介绍的BeanDefinitionParseDelegate中完成的。
    Spring 源码分析--IOC的详细过程_第12张图片
    可以看到具体的读取过程可以在doLoadBeanDefinitions方法中找到,这是从特定的XML文件中载入BeanDefinition的地方。在这个方法中会看到熟悉的解析XML的常用API,比如获取Document,SAX解析等。
    Spring 源码分析--IOC的详细过程_第13张图片
    在registerBeanDefinitions(doc, resource)中进行了详细的解析过程,这个解析会使用到spring的Bean配置规则。
    Spring 源码分析--IOC的详细过程_第14张图片
    解析过程是在documentReader.registerBeanDefinitions(doc, createReaderContext(resource))中完成的,对于BeanDefinition的处理结果是由BeanDefinitionHolder对象来持有它,当然还包括其他与BeanDefinition的使用相关的信息,比如Bean的名字、别名集合等,这个BeanDefinitionHolder的生成是通过对Document文档树的内容进行解析来完成的,如前所述这个解析过程是由BeanDefinitionParseDelegate来实现的
    Spring 源码分析--IOC的详细过程_第15张图片
    最后的解析结果是BeanDefinitionHolder中封装了BeanDefinition的各种信息,名称和别名属性元素,这会在后面的注册过程中使用到,因为注册的Map的key是BeanDefinition的名字,这个名字要在BeanDefinitionHolder拿到。那么我们这时候看下BeanDefinition到底张什么样子
    Spring 源码分析--IOC的详细过程_第16张图片
    你可以在它的基类AbstractBenDefinition中暗道很多常见的Spring标记,如lazyinit、scope、primary等。关于具体的处理过程,不再详述。
    Spring 源码分析--IOC的详细过程_第17张图片
    通过层层解析,XML文件中定义的BeanDefinition就被整个载入到了IOC容器中,并在容器中建立了数据映射,现在IOC容器中的BeanDefinition中存在的还只是一些静态的配置信息,这时候容器还没有起作用,要发挥作用,还需完成数据向容器的注册
    BeanDefinition在IOC容器中的注册
    在DefaultListableBeanFactory中是通过一个HashMap来持有载入的BeanDefinition的,如下:
private final Map beanDefinitionMap = new ConcurrentHashMap(64);

将解析得到的BeanDefinition向IOC容器中的beanDefinitionMap注册的过程是在载入后完成的,注册的过程如下:
Spring 源码分析--IOC的详细过程_第18张图片
在processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)会完成BeanDefinition的注册,如下:

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}
@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		synchronized (this.beanDefinitionMap) {
			oldBeanDefinition = this.beanDefinitionMap.get(beanName);
			if (oldBeanDefinition != null) {
				if (!this.allowBeanDefinitionOverriding) {
					throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
							"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
							"': There is already [" + oldBeanDefinition + "] bound.");
				}
				else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
					// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
					if (this.logger.isWarnEnabled()) {
						this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
								" with a framework-generated bean definition ': replacing [" +
								oldBeanDefinition + "] with [" + beanDefinition + "]");
					}
				}
				else {
					if (this.logger.isInfoEnabled()) {
						this.logger.info("Overriding bean definition for bean '" + beanName +
								"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
					}
				}
			}
			else {
				this.beanDefinitionNames.add(beanName);
				this.frozenBeanDefinitionNames = null;
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

可以看到BeanDefinition的注册都和声明的Map有关,而且会判断是否可以覆盖,其会先去根据beanDefinition的名字去Map中取,然后判断是否允许覆盖,最后添加。

你可能感兴趣的:(Spring源码分析,spring,java)