1. 简介
前面一篇文章,分析了 Spring IOC 容器的一些特性。从这篇文章开始,我们会开始进入源码的分析阶段。
本篇文章会带大家先俯瞰 IOC 容器启动的整个过程,让读者先从整体上对 IOC 容器有个大体的认识。后面的文章,我会对容器启动过程中关键的点,进行深入的分析。好了,废话少说,下面开始看源码。
2. BeanFactory 和 ApplicationContext
在开始阅读源码之前,我们先要了解 IOC 相关的核心几个类的结构。
BeanFactory 是 Spring 框架中最核心的接口,我们要讲的 IOC 容器通常就是指 BeanFactory,它负责生产和维护 Bean。而 ApplicationContext 是对 BeanFactory 的拓展,提供了更多面向应用的功能。一般来说,BeanFactory 是面向 Spring 本身,而ApplicationContext 面向我们开发者。下面我们一起来看下这两个接口的体系结构吧。
BeanFactory 类继承结构图:
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()
这个方法。后面的文章会带着大家继续分析后面的流程,由于作者水平有限,其中有错误的地方,还望大家指出。好了,感谢大家的阅读!