SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

文章目录

一、手写Spring

篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122625811

二、Spring IoC高级应用面试常问知识点复习

篇幅限制,我将其放在这篇文章中:https://blog.csdn.net/grd_java/article/details/122647693

三、Spring IoC源码

1. 源码剖析的方法和注意事项

注意事项
  1. 原则
  1. 定焦原则:学会抓主线(我会给出主线的流程图),主线外的东西我们不管(否则丢了西瓜捡芝麻,到头来上面都没读懂)。
  2. 宏观原则:关注源码结构和业务流程,不要死扣具体某行代码
  1. 技巧
  1. 断点,多观察调用栈
  2. 反调,看看哪些地方调用了当前方法
  3. 终结经验,比如spring中很多doXXXX的方法,这些方法是干具体工作的,寻找类似这样的spring中编程习惯
  1. 确保你看的是源码(.java文件),不是字节码文件(.class文件),源码可以直接去github上下载,或者使用maven动态下载(看什么下载什么)
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
接下来我会在IoC容器初始化主体流程,BeanFactory及容器继承体系中,完整演示如何看源码(带着大家一步步走)。因为完整的带着大家看,及其废篇幅,所以之后的其它的知识,只会给出流程图,以及注意事项,重点总结。看源码大家自己跟着流程图看就好,我只会截出重点源代码

2. IoC容器初始化主体流程

根据ClassPathXmlApplicationContext深入源码
我们先搞一个bean,xml和测试类,用来作为读源码的入口
  1. Bean
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 配置到xml中
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  3. 测试类
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

2.1 BeanFactory及容器继承体系

为什么我们常用ApplicationContext,而不是直接用BeanFactory呢?
  1. ApplicationContext是容器的高级接口,继承于BeanFactory接口(*容器/根容器,规范了/定义了容器的基础行为)
  2. Spring应用上下文,官方称之为IoC容器(并不仅仅是一个map而已,map是IoC容器的一个成员,称为单例池,singletonObject)
  3. 容器是一组组件和过程的集合,包括BeanFactory、单例池、BeanPostProcessor等,以及它们之间的协作过程
继承体系(可以发现ApplicationContext除了BeanFactory,还继承了很多其它接口,功能非常丰富)

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

  1. BeanFactory
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. ListableBeanFactory,规定了很多批量返回的操作
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  3. HierarchicalBeanFactory,规定了一些Bean工厂的操作,返回父Bean工厂和判断Bean工厂是否有指定实例
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  4. MessageSource ,规定了国际化的一些操作
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  5. ResourceLoader,规定了加载资源的操作
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
上面的内容我是怎么看的呢?
  1. 假如看BeanFactory*容器,它是一接口,所以我们只需要查看它定义了哪些抽象方法和常量即可,因为接口也定义不了其它什么东西了
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

2.2 Bean周期关键时机点代码调用分析

Bean的生命周期如下:
Created with Raphaël 2.3.0 第一步:实例化Bean 第二步:设置属性值 第三步:调用BeanNameAware的setBeanName方法 第四步:调用BeanFactoryAware的setBeanFactory方法 第五步:调用ApplicationContextAware的setApplicationContext方法 第六步:调用BeanPostProcessor的预初始化方法 第七步:调用InitializingBean的afterPropertiesSet方法 第八步:调用定制的初始方法init-method 第九步:调用BeanPostProcessor的后初始化方法
  1. 第九步,有两种可能,如果是prototype(原型模式)的bean,就直接交给调用者
  2. 如果是singleton(单例模式)的bean,就放入spring缓存池中准备就绪的bean
Created with Raphaël 2.3.0 Spring缓存池中准备就绪的bean,当Bean销毁时 调用destory-method属性配置的销毁方法(没有先后顺序) 调用DisposableBean的destory方法(没有先后顺序)
我们要关注的关键时机点如下
  1. Bean的构造方法和第七步:调用InitializingBean的afterPropertiesSet方法
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 后置处理器BeanFactoryPostProcessor
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  3. 后置处理器BeanPostProcessor
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  4. 别忘了配置xml
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
流程图

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

1. Bean构造方法
  1. 打断点,看调用栈
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 调用refresh方法中的finishBeanFactoryInitialization方法完成Bean实例化,调用构造方法,AbstractApplicationContext#refresh#finishBeanFactoryInitialization
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
2. 生命周期第七步:调用InitializingBean的afterPropertiesSet方法
  1. 打断点,看调用栈
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 和构造方法一样,调用refresh方法中的finishBeanFactoryInitialization方法完成Bean实例化,调用构造方法,AbstractApplicationContext#refresh#finishBeanFactoryInitialization
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
3. 后置处理器BeanFactoryPostProcessor
  1. 构造方法打断点,看调用栈
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 调用了AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors,所以Bean工厂后置处理器初始化就在这里
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  3. 处理方法打断点,看调用栈
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  4. 调用了AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors,所以Bean工厂后置处理器处理方法也在这里
4. 后置处理器BeanPostProcessor
  1. 构造函数,AbstractApplicationContext#refresh#registerBeanPostProcessors
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 处理方法postProcessBeforeInitialization和postProcessAfterInitialization,都是在AbstractApplicationContext#finishBeanFactoryInitialization
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
可以发现,看流程图就完事了,压根没必要把代码贴出来,大家自己看就好,下面会介绍如何一步步DeBug

2.3 refresh方法

从上面可以发现,基本Bean周期,都是进入refresh方法,我们这里关注这个方法,干了什么事(主线),和主线无关的不要管(千万不要贪),滤清了整体思路,我们后面再扣细节
流程图

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

源码debug
  1. ClassPathXmlApplicationContext;断点,f7进入方法
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 可见一进来就加载了ContextClosedEvent,官方解释是避免奇怪的类加载问题。然后我们shift+f8跳出
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  3. 再次f7步入,进入构造方法中,发现调用了另一个构造函数,我们继续f7步入
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  4. 构造方法中,先初始化了父类(super(parent)),处理配置文件路径。我们通过f8略过这个方法,直接往下执行,然后它调用setConfigLocations方法设置本地配置信息(可以自己f7步入,看一下,然后shift+f8步出),最后调用refresh()完成Spring容器的初始化,我们f7步入这个方法
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  5. 我们发现refresh()中所有代码都在锁中(锁的是一个对象synchronized (this.startupShutdownMonitor) ),我们通过右键鼠标,FindUsages或者直接快捷键Alt+f7对这个对象反调,可以发现,除了初始化,close关闭的时候,也用了这把锁(也就是说,初始化时,不可以同时close关闭),然后我们继续f8略过,往下走一步
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  6. 发现其调用prepareRefresh方法,进行刷新(初始化)前的预处理,设置Spring容器启动时间,开启活跃状态,撤销关闭状态,验证环境信息里一些必要属性等等,继续f8
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    告诉子类刷新内部bean工厂(初始化)
    获取BeanFactory,默认实现是DefaultListableBeanFactory
    加载BeanDefition(xml,配置文件中的信息封装)并注册到BeanDefitionRegistry
  8. prepareBeanFactory(beanFactory);
    准备在此上下文中使用的bean工厂
    BeanFactory的预备工作,BeanFactory进行一些设置,比如context的类加载器等
  9. 剩下的都一样,用上面介绍的快捷键,自己跟着流程图看吧

3. IoC容器初始化子流程(细节)

从上面主体流程中,我们发现两个需要特别关注的点
  1. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();告诉子类刷新内部bean工厂,主要干两件事,获取BeanFactory,加载BeanDefition

3.1 BeanFactory获取

流程图

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

时序图

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

3.2 BeanDefinition加载注册

上面对于BeanFactory的获取流程,我们发现在初始化BeanFactory完成前,调用loadBeanDefinitions(beanFactory);加载应用中的BeanDefinitions
流程图
  1. 发现循环加载xml配置文件的机制
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. 发现循环加载统一封装资源对象Resource的机制
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  3. 发现最终是将将xml文件流封装为InputSource对象
    然后调用doLoadBeanDefinitions(inputSource, encodedResource.getResource())执行加载逻辑,
    而doLoadBeanDefinitions中先是读取xml信息,保存到Document对象中
    然后解析document对象,封装BeanDefinition对象并注册
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

4. Bean对象创建流程

refresh方法中调用的finishBeanFactoryInitialization(beanFactory);为Bean创建子流程入口
流程图

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

5. lazy-init懒加载机制

AbstractApplicationContext#refresh#finishBeanFactoryInitialization#preInstantiateSingletons#isLazyInit

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

  1. 如果是懒加载,就不创建bean
接下来我们通过getBean一个开启懒加载的bean,打断点看一下流程

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

直接给出总结,因为很简单
  1. 和Bean对象创建流程唯一不一样,就是创建时机
  2. 统一最后调用doGetBean()方法创建Bean实例
  3. 只不过开启懒加载的,会在上面的isLazyInit方法进行判断,从而不在初始化时创建bean而已
  4. 而创建时机是我们getBean()的时候

6. 循环依赖问题

循环依赖

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

  1. 并不是函数内种循环调用,而是对象间的相互依赖,就是一个死循环,除非有终结条件
Spring 中循环依赖场景有
  1. 构造器的循环依赖(构造器注入)
  2. Field属性的循环依赖(set注入)
  3. 其中,构造器的循环依赖问题无法解决,只能抛出BeanCurrentlyInCreationException异常,而解决属性循环依赖时,spring采用提前暴露对象的方法
spring对各种循环依赖的处理
  1. 单例bean构造器参数循环依赖,无法解决
  2. prototype原型bean循环依赖,无法解决(这个bean创建出来以后,压根不归IoC管)
  3. 对应原型Bean的初始过程,无论通过构造器参数还是setXxx方法参数循环依赖,Spring都会直接报错
//AbstractBeanFactory.doGetBean()方法
if(isPrototypeCurrentlyInCreation(beanName)){
	throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName){
	Object curVal = this.prototypesCurrentlyInCrreation.get();
	return (curVal != null && (curVal.equals(beanName)||(curVal instanceof Set && ((Set<?>)curVal).contains(beanName))));
}
  1. 获取bean之前如果这个Bean正在被创建则直接抛异常,原型Bean在创建之前会进行标记这个beanName正在被创建,等创建结束后会删除标记(总之,原型设计模式的bean,不支持循环依赖)
try{
	//创建原型Bean之前添加标记
	beforeProrotypeCreation(beanName);
	//创建原型bean
	prototypeInstance = createBean(beanName,mbd,args);
}finally{
	//创建原型bean之后删除标记
	afterPrototypeCreation(beanName);
}
  1. 单例bean通过setXxx或@Autowired进行循环依赖,可以解决
  1. 基于java引用传递,当获得对象的引用时,对象的属性可以延后设置,但是构造器必须是在获取引用之前
  2. Spring 通过setXxx或@Autowired方法解决循环依赖,其实是通过提前暴露一个ObjectFactory对象来完成,简单来说就是ClassA调用完构造器完成对象初始化后,再调用ClassA的setClassB方法之前就把,ClassA实例化的对象,通过ObjectFactory提前暴露到Spring容器中
解决方案之三级缓存: spring存放Bean不是全放在一个map中,而是分3个级别的缓存

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

  1. 一级缓存中的Bean是完全可用的Bena,其它缓存的Bean是还未初始化完成的Bean
  1. 假设我们有Result类和B类相互依赖
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
  2. Result对象实例化之后,会立马放入三级缓存(提前暴露自己),此时属性并没有赋值,并且标记为正在创建
  3. 此时发现Result依赖于B,三级缓存还没有B的实例,于是实例化B(也会在实例化之后,立即放入三级缓存)
  4. 实例化B时,发现B依赖于Result,B去三级缓存找,找到了,将Result注入,然后Result进入二级缓存(升级到二级缓存过程中,会对Result做一些扩展操作)
  5. B对象实例化完成,放入一级缓存中
  6. Result对象去一级缓存找B,然后将B注入
源码分析,流程图,我们从Bean对象创建流程的doCreateBean开始追

SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来

  1. 流程图如下
    SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来
上一篇:Spring系列7:`autowire`自动装配怎么玩


下一篇:源码解读Spring如何解决循环依赖