Spring IOC 容器源码分析 02 - IOC 容器启动过程分析

1. 简介

前面一篇文章,分析了 Spring IOC 容器的一些特性。从这篇文章开始,我们会开始进入源码的分析阶段。

本篇文章会带大家先俯瞰 IOC 容器启动的整个过程,让读者先从整体上对 IOC 容器有个大体的认识。后面的文章,我会对容器启动过程中关键的点,进行深入的分析。好了,废话少说,下面开始看源码。

2. BeanFactory 和 ApplicationContext

在开始阅读源码之前,我们先要了解 IOC 相关的核心几个类的结构。

BeanFactory 是 Spring 框架中最核心的接口,我们要讲的 IOC 容器通常就是指 BeanFactory,它负责生产和维护 Bean。而 ApplicationContext 是对 BeanFactory 的拓展,提供了更多面向应用的功能。一般来说,BeanFactory 是面向 Spring 本身,而ApplicationContext 面向我们开发者。下面我们一起来看下这两个接口的体系结构吧。

BeanFactory 类继承结构图:
BeanFactory 继承结构图
ApplicationContext 类继承结构图:
ApplicationContext 类继承结构图
看到这些类图,读者是不是已经有点晕了。Spring 为了适应各种使用场景,提供的各个接口都可能有很多的实现类。这里全部列出来,只是为了让大家有个印象,后面我们分析源码的时候,基本都是在这些类中。这里挑几个重点的类说下:

  • DefaultListableBeanFactory 这个应该是最强的 BeanFactory了,因为继承了上面所有的接口,后面我们用到的也是这个 BeanFactory
  • ClassPathXmlApplicationContext 这个类在我们前面使用过,也是我们常用的 ApplicationContext,从类路径中加载配置文件,启动容器
  • FileSystemXmlApplicationContext 这个类功能跟 ClassPathXmlApplicationContext 类似,只是是从文件系统中加载配置文件
  • AnnotationConfigApplicationContext 是基于注解的,是通过加载 Java 配置类启动容器

3. 俯瞰启动过程

好了,介绍完类结构图我们开始来跟踪一个 IOC 容器启动过程,首先我们使用 ClassPathXmlApplicationContext 来启动一个 IOC 容器:

@Test
public void test5() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
}

然后我们进入 ClassPathXmlApplicationContext 源码看下,这句简单的代码里面到底做了哪些事,为什么能启动整个 IOC 容器。

// ClassPathXmlApplicationContext.java
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
	@Nullable
	private Resource[] configResources;
	
	public ClassPathXmlApplicationContext() {}

	public ClassPathXmlApplicationContext(ApplicationContext parent) {
		super(parent);
	}
	
	// 我们调用的构造方法
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

	public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
		this(configLocations, true, null);
	}

	public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent)
			throws BeansException {
		this(configLocations, true, parent);
	}

	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
		this(configLocations, refresh, null);
	}
	
	// 最终调用的是这个方法
	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh,@Nullable ApplicationContext parent) throws BeansException {
		super(parent);
		// 设置配置文件地址信息
		setConfigLocations(configLocations);
		if (refresh) {
			// 核心方法,刷新IOC容器
			refresh();
		}
	}
	// 省略无关代码
	// ......
}

ClassPathXmlApplicationContext 类有很多重载的构造方法,我们调用的方法最终调用了 ClassPathXmlApplicationContext(String[], boolean, ApplicationContext parent) 这个方法,这个方法主要就是调用了 refresh() 方法,后面我们会知道,refresh() 是启动 IOC 容器最核心的一个方法,我们继续到 refresh() 方法中看看。

// AbstractApplicationContext.java line:515
@Override
public void refresh() throws BeansException, IllegalStateException {
	// 做个同步锁,防止一个容器还没启动完成,又启动了另外的容器
	synchronized (this.startupShutdownMonitor) {
		// 容器刷新前的准备工作
		prepareRefresh();
		// 这部会将配置文件中的bean配置信息解析成BeanDefinition,后面有专门的章节分析
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		// 设置类加载器,注册特殊的几个bean等,后面会展开说
		prepareBeanFactory(beanFactory);
		try {
			// 注册BeanFactoryPostProcessor
			postProcessBeanFactory(beanFactory);

			// 调用BeanFactoryPostProcessor
			invokeBeanFactoryPostProcessors(beanFactory);

			// 注册BeanPostProcessor
			registerBeanPostProcessors(beanFactory);

			// 初始化MessageSource,这个方法不是主流程,后面不会分析
			initMessageSource();

			// 初始化当前ApplicationContext的事件广播器,这里也不展开了
			initApplicationEventMulticaster();

			// 这里是模版方法,留给子类实现
			onRefresh();

			// 注册事件监听器,这个也不是我们的重点,也不做展开了
			registerListeners();

			// 重点方法,所有的singleton bean都在这里实例化的,后面会专门分析
			finishBeanFactoryInitialization(beanFactory);

			// 做一些后续处理和广播工作
			finishRefresh();
		} catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization cancelling refresh attempt: " + ex);
			}
			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();
			// Reset 'active' flag.
			cancelRefresh(ex);
			// Propagate exception to caller.
			throw ex;
		} finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

这里,大家也许会好奇,这里叫 refresh() 方法,而为什么不是叫 init() 之类的方法。因为 ApplicationContext 建立起来之后,是可以通过调用 refresh() 来重新构建的,把之前的 ApplicationContext 销毁,然后重写执行初始化流程。

源码中的每行代码我都有详细的注释,大家应该都能看明白,这个方法里面又调用了很多的方法,后面我会详细的分析关键的几个方法,像 initMessageSource()initApplicationEventMulticaster()registerListeners()finishRefresh() 这类跟主流程关系不是很大的方法,我会略过。Spring 经过十几年的发展,功能和代码都是很复杂的,限于篇幅和读者能力,我只会分析主流程的关键代码,其他无关代码,感兴趣的读者可以自己阅读。

4. 启动前的准备工作

下面我们进入到 prepareRefresh() 方法中看容器启动前做了哪些准备工作:

// AbstractApplicationContext.java line:583
protected void prepareRefresh() {
		// 记录启动时间,切换到active状态
		this.startupDate = System.currentTimeMillis();
		this.closed.set(false);
		this.active.set(true);

		// ...... 省略无关代码

		// 初始化占位符属性相关
		initPropertySources();

		// 校验配置文件
		getEnvironment().validateRequiredProperties();

		// ...... 省略无关代码
	}

这个方法比较简单,没什么特别说明的,主要是容器启动前的一些准备设置。

4. 总结

本篇文章,对 IOC 容器核心类 BeanFactory、ApplicationContext 的体系结构做了个了解,然后俯瞰的 IOC 容器启动的整个过程,最后分析了 prepareRefresh() 这个方法。后面的文章会带着大家继续分析后面的流程,由于作者水平有限,其中有错误的地方,还望大家指出。好了,感谢大家的阅读!

发布了8 篇原创文章 · 获赞 7 · 访问量 708

猜你喜欢

转载自blog.csdn.net/hao_yan_bing/article/details/102881287