springboot启动流程(七)ioc容器refresh过程(上篇)
所有文章
https://www.cnblogs.com/lay2017/p/11478237.html
正文
在前面的几篇文章中,我们看到Environment创建、application配置文件的加载、ApplicationContext实例对象的创建、以及主类加载成为BeanDefinition。做了这么多的准备,终于到了核心的部分,也就是ioc容器的刷新。
这里,我们不免要再次回顾一下SpringAplication的run方法
public ConfigurableApplicationContext run(String... args) {
// 声明一个Context容器
ConfigurableApplicationContext context = null;
// 获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 调用监听器的启动
listeners.starting();
try {
// 创建并配置Environment(这个过程会加载application配置文件)
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 根据应用类型创建对应的Context容器
context = createApplicationContext();
// 刷新Context容器之前的准备
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新Context容器
refreshContext(context);
// 刷新Context容器之后处理
afterRefresh(context, applicationArguments);
// Context容器refresh完毕发布
listeners.started(context);
// 触发Context容器refresh完以后的执行
callRunners(context, applicationArguments);
} catch (Throwable ex) {}
try {
// Context启动完毕,Runner运行完毕发布
listeners.running(context);
} catch (Throwable ex) {}
return context;
}
run方法中,prepareContext和afterRefresh之间的refreshContext方法正是ioc容器刷新的入口方法。
ApplicationContext和BeanFactory
但是在阅读refreshContext方法之前,我们得先区分一下ApplicationContext和BeanFactory两者之间的关系。在之前的文章中,我们并没有把二者进行区分。比如,我们总是说"把Bean注册到ApplicationContext容器"。
在我们的理解中,容器应该是一个空间的概念,用于存放事物的东西。在spring中,存放的是Bean。而BeanFactory提供了这么一个空间用于存放Bean,所以BeanFactory才是Bean所在的主要容器,而不是我们一直说的ApplicationContext。
既然ApplicationContext不是容器,那它又是啥呢?我们称之为"上下文"。"上下文"的概念我们也许不见得那么熟,但是"场景","场所"这样的概念我们应该就比较熟悉了。比如说"拍摄场景","交易场所"等。它们的共同点都是事件发生的地方。所以ApplicationContext正是spring定义的应用程序的事件发生场所,也就是所谓的应用上下文。
上面,我了解了BeanFactory作为Bean容器,而ApplicationContext作为上下文。那么Bean容器和上下文之间是什么关系呢?我们可以看一个代码片段
// 通用的应用上下文实现
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
// 默认BeanFactory的实现
private final DefaultListableBeanFactory beanFactory; // 省略
}
我们看到BeanFactory是被组合在ApplicationContext当中的,所以它们的其中一种关系就是组合关系。也就是说应用上下文中包含着Bean工厂。
接着,我们分别看看ApplicationContext的类图

我们看到ApplicationContext和BeanFactory还存在着继承关系,这意味着ApplicationContext可以对外被当做BeanFactory来使用,这也是为什么我们总是把ApplicationContext当做容器来看的主要原因,因为对外来看两者是一体的。
结合上面的组合关系,我们可以知道对内的话ApplicationContext的BeanFactory相关实现会由内部组合的BeanFactory的实现类来完成具体工作。
到这里,我们基本就明白了ApplicationContext和BeanFactory之间的关系有两种:组合、继承。
后面我们将称呼ApplicationContext为上下文,而BeanFactory为Bean容器,进行区分。
上下文组合Bean工厂
那么BeanFactory是什么时候被组合到ApplicationContext当中的呢?我们先看看ApplicationContext的实现类的类图
注意!springboot默认的servlet项目的ApplicationContext实现类是AnnotationCofnigServletWebServerApplicationContext,所以我们会以它作为实现类来看

我们自下而上,顺着继承链找到GenericApplicationContext我们就会看到之前出现过的代码片段
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
// 省略
}
这里,在GenericApplicationContext的构造方法当中构建了一个DefaultListableBeanFactory的实例对象。DefaultListableBeanFactory也就是BeanFactory的默认实现,那么也就是说在构造ApplicationContext实例对象的时候创建并组合了一个BeanFactory的实例。
我们顺便也看看DefaultListableBeanFactory的继承关系吧

这个类图只保留了BeanFactory的东西,设计路线自BeanFactory到DefaultListableBeanFactory也很清晰。
refreshContext刷新过程
下面,我们将正式进行refreshContext方法的阅读。打开refreshContext方法
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
跟进refresh方法
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
我们看到,这里调用的是AbstractApplicationContext的refresh方法,顺序AnnotationConfigServletWebServerApplicationContext的继承链向上可以找到AbstractApplicationContext。
我们继续跟进AbstractApplicationContext的refresh方法,refresh方法有点长
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 刷新前准备,设置flag、时间,初始化properties等
prepareRefresh();
// 获取ApplicationContext中组合的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 设置类加载器,添加后置处理器等准备
prepareBeanFactory(beanFactory);
try {
// 供子类实现的后置处理
postProcessBeanFactory(beanFactory);
// 调用Bean工厂的后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册Bean的后置处理器
registerBeanPostProcessors(beanFactory);
// 初始化消息源
initMessageSource();
// 初始化事件广播
initApplicationEventMulticaster();
// 供之类实现的,初始化特殊的Bean
onRefresh();
// 注册监听器
registerListeners();
// 实例化所有的(non-lazy-init)单例Bean
finishBeanFactoryInitialization(beanFactory);
// 发布刷新完毕事件
finishRefresh();
}
catch (BeansException ex) {
//
} finally {
//
}
}
}
在上一篇文章中,我们提到了这么一个初始化过程:
annotation或者xml中Bean的配置 --> 内存中的BeanDefinition --> Bean
也就是实际的配置,转化成内存中的配置对象,再根据配置对象转化成具体的实例对象。说白了就是从元数据到实例的一个转化过程。
为什么会提及这么一个转化过程呢?因为我们的refresh过程主要包含的就是其中的一步,也就是从annotation或者xml的Bean配置 --> 内存中的BeanDefinition的过程。这个过程的实现在调用Bean工厂的后置处理器的时候完成,也就是invokeBeanFactoryPostProcessors方法
我们跟进invokeBeanFactoryPostProcessors方法
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
//
}
这里先获取了所有后置处理器,然后调用处理。再跟进PostProcessorRegistrationDelegate的invokeBeanFactoryFactoryPostProcessors方法
该方法很长,我们删减掉大部分内容
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory,
List<BeanFactoryPostProcessor> beanFactoryPostProcessors
) {
//
if (beanFactory instanceof BeanDefinitionRegistry) {
//
while (reiterate) {
// 调用BeanDefinition注册的后置处理器
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
//
}
//
} else {
//
}
//
}
可以看到,调用后置处理器的时候会调用到注册BeanDefinition的后置处理器。也就是从这里开始作为BeanDefinition的注册入口
跟进invokeBeanDefinitionRegistryPostProcessors
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors,
BeanDefinitionRegistry registry
) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
这里我们需要断点一下,看看有哪些后置处理器处理BeanDefinition注册

我们看到了ConfigurationClassPostProcessor也就是它完成BeanDefinition注册这项工作的
我们跟进ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 省略
processConfigBeanDefinitions(registry);
}
继续跟进,我们看到processConfigBeanDefinitions方法挺长的,进行了大量的缩减
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
//
} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 默认仅有主类被添加
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
//
// 解析被 @Configuration 注解的类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory,
this.problemReporter,
this.environment,
this.resourceLoader,
this.componentScanBeanNameGenerator,
registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
//
do {
// 解析的核心方法
parser.parse(candidates);
parser.validate();
//
candidates.clear();
//
} while (!candidates.isEmpty());
//
}
上一篇文章中,也就是在prepareContext方法的核心逻辑里,main方法所在的主类将会被作为BeanDefinition加载到BeanFactory当中。而在这里,该主类将被作为一个配置类被解析,解析器即ConfigurationClassParser。
我们跟进parse方法看看
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
// 主类的解析将从这里进入
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
} else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
} else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
} catch (BeanDefinitionStoreException ex) {}
catch (Throwable ex) {}
}
this.deferredImportSelectorHandler.process();
}
继续跟进parse方法
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
可以看到主类作为配置类的解析过程将从processConfigurationClass这里开始
总结
到这里,ioc容器的refresh过程先做一个小结。我们知道了上下文和Bean容器是继承关系又是组合关系。refreshContext的核心就是为了加载BeanDefinition,而加载BeanDefinition将从main方法所在的主类开始,主类作为一个配置类将由ConfigurationClassParser解析器来完成解析的职责。下一篇文章,我们将会看到从主类中解析出BeanDefinition的主要逻辑。
springboot启动流程(七)ioc容器refresh过程(上篇)的更多相关文章
- springboot启动流程(八)ioc容器refresh过程(下篇)
所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 正文 上一篇文章,我们知道了解析过程将从解析main方法所在的主类开始.在文章的最后我们稍微看 ...
- springboot启动流程(目录)
springboot出现有段时间了,不过却一直没有怎么去更多地了解它.一方面是工作的原因,另一方面是原来觉得是否有这个必要,但要持续做java似乎最终逃不开要去了解它的命运.于是考虑花一段时间去学习一 ...
- SpringBoot启动流程分析(四):IoC容器的初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(六):IoC容器依赖注入
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(一):SpringApplication类初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(二):SpringApplication的run方法
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程解析
写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...
随机推荐
- [原][OSG][osgEarth]osgEarth例子程序简介
1.osgearth_graticule:生成经纬线. 2.osgearth_annotation:各类标注(点.线.面.模型.文本等). 3.osgearth_city:加载一个城市三维模型,可以浏 ...
- 机器学习 - 算法 - 聚类算法 K-MEANS / DBSCAN算法
聚类算法 概述 无监督问题 手中无标签 聚类 将相似的东西分到一组 难点 如何 评估, 如何 调参 基本概念 要得到的簇的个数 - 需要指定 K 值 质心 - 均值, 即向量各维度取平均 距离的度量 ...
- 解决kali无法连接网络问题
键入 ifconfig -a 查看网卡 ,是否存在 键入 leafpad /etc/network/interfaces 查看其中是否有如下语句,没有添加上即可:auto eth0iface eth0 ...
- lvs,nginx,haproxy的优缺点,适合场景
Nginx/LVS/HAProxy的基于Linux的开源免费的负载均衡软件. LVS:使用集群技术和Linux操作系统实现一个高性能.高可用的服务器,它具有很好的可伸缩性.可靠性和可管理性,是一款强大 ...
- 【420】链表实现Quack
quack.h // quack.h: an interface definition for a queue/stack #include <stdio.h> #include < ...
- shuffle 打乱一维数组
<?php $arr = range(,); print_r($arr); echo '<br />'; shuffle($arr); print_r($arr); ?> Ar ...
- Ctrl + 逗号快捷键被占用[搜狗输入法]
Ctrl+,(或者Ctrl+逗号)被占用. 快捷键忽然不能用了,只要一用快捷键自动唤醒搜狗输入法,呵呵.极度影响使用. 就说怎么禁掉吧: 其他快捷键禁用参考 参考: 搜狗桌面论坛 注:搜狗输入法一次占 ...
- 6年DotNet码农的盲目经历
前言 第一篇没有选择记录与技术相关的文档,是考虑到有必要给查阅这篇文档的伙伴们“自我介绍”一下,大佬们看了求带或指导,我很愿意学习,初学者们看了千万不要重复走我之前的“学习之路”:我老家贵州,再过 ...
- springboot使用activemq同时接收queue和topic消息
原文链接:https://blog.csdn.net/jia_costa/article/details/79354478 新建springboot项目, pom文件如下 <?xml versi ...
- python计算平面的法向-利用协方差矩阵求解特征值和特征向量
Obvious,最小特征值对应的特征向量为平面的法向 这个问题还有个关键是通过python求协方差矩阵的特征值和特征向量,np.linalg.eig()方法直接返回了特征值的向量和特征向量的矩阵 sc ...