Java教程

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

本文主要是介绍SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 一、手写Spring
  • 二、Spring IoC高级应用面试常问知识点复习
  • 三、Spring IoC源码
    • 1. 源码剖析的方法和注意事项
    • 2. IoC容器初始化主体流程
      • 2.1 BeanFactory及容器继承体系
      • 2.2 Bean周期关键时机点代码调用分析
      • 2.3 refresh方法
    • 3. IoC容器初始化子流程(细节)
      • 3.1 BeanFactory获取
      • 3.2 BeanDefinition加载注册
    • 4. Bean对象创建流程
    • 5. lazy-init懒加载机制
    • 6. 循环依赖问题

一、手写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动态下载(看什么下载什么)
    在这里插入图片描述
    在这里插入图片描述
接下来我会在IoC容器初始化主体流程,BeanFactory及容器继承体系中,完整演示如何看源码(带着大家一步步走)。因为完整的带着大家看,及其废篇幅,所以之后的其它的知识,只会给出流程图,以及注意事项,重点总结。看源码大家自己跟着流程图看就好,我只会截出重点源代码

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

根据ClassPathXmlApplicationContext深入源码
我们先搞一个bean,xml和测试类,用来作为读源码的入口
  1. Bean
    在这里插入图片描述
  2. 配置到xml中
    在这里插入图片描述
  3. 测试类
    在这里插入图片描述

2.1 BeanFactory及容器继承体系

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

在这里插入图片描述

  1. BeanFactory
    在这里插入图片描述
  2. ListableBeanFactory,规定了很多批量返回的操作
    在这里插入图片描述
  3. HierarchicalBeanFactory,规定了一些Bean工厂的操作,返回父Bean工厂和判断Bean工厂是否有指定实例
    在这里插入图片描述
  4. MessageSource ,规定了国际化的一些操作
    在这里插入图片描述
  5. ResourceLoader,规定了加载资源的操作
    在这里插入图片描述
上面的内容我是怎么看的呢?
  1. 假如看BeanFactory顶级容器,它是一接口,所以我们只需要查看它定义了哪些抽象方法和常量即可,因为接口也定义不了其它什么东西了
    在这里插入图片描述

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方法
    在这里插入图片描述
  2. 后置处理器BeanFactoryPostProcessor
    在这里插入图片描述
  3. 后置处理器BeanPostProcessor
    在这里插入图片描述
  4. 别忘了配置xml
    在这里插入图片描述
流程图

在这里插入图片描述

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

2.3 refresh方法

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

源码debug
  1. ClassPathXmlApplicationContext;断点,f7进入方法
    在这里插入图片描述
  2. 可见一进来就加载了ContextClosedEvent,官方解释是避免奇怪的类加载问题。然后我们shift+f8跳出
    在这里插入图片描述
    在这里插入图片描述
  3. 再次f7步入,进入构造方法中,发现调用了另一个构造函数,我们继续f7步入
    在这里插入图片描述
    在这里插入图片描述
  4. 构造方法中,先初始化了父类(super(parent)),处理配置文件路径。我们通过f8略过这个方法,直接往下执行,然后它调用setConfigLocations方法设置本地配置信息(可以自己f7步入,看一下,然后shift+f8步出),最后调用refresh()完成Spring容器的初始化,我们f7步入这个方法
    在这里插入图片描述
  5. 我们发现refresh()中所有代码都在锁中(锁的是一个对象synchronized (this.startupShutdownMonitor) ),我们通过右键鼠标,FindUsages或者直接快捷键Alt+f7对这个对象反调,可以发现,除了初始化,close关闭的时候,也用了这把锁(也就是说,初始化时,不可以同时close关闭),然后我们继续f8略过,往下走一步
    在这里插入图片描述
    在这里插入图片描述
  6. 发现其调用prepareRefresh方法,进行刷新(初始化)前的预处理,设置Spring容器启动时间,开启活跃状态,撤销关闭状态,验证环境信息里一些必要属性等等,继续f8
    在这里插入图片描述
    在这里插入图片描述
  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获取

流程图

请添加图片描述

时序图

在这里插入图片描述

3.2 BeanDefinition加载注册

上面对于BeanFactory的获取流程,我们发现在初始化BeanFactory完成前,调用loadBeanDefinitions(beanFactory);加载应用中的BeanDefinitions
流程图
  1. 发现循环加载xml配置文件的机制
    请添加图片描述
  2. 发现循环加载统一封装资源对象Resource的机制
    请添加图片描述
  3. 发现最终是将将xml文件流封装为InputSource对象
    然后调用doLoadBeanDefinitions(inputSource, encodedResource.getResource())执行加载逻辑,
    而doLoadBeanDefinitions中先是读取xml信息,保存到Document对象中
    然后解析document对象,封装BeanDefinition对象并注册
    请添加图片描述

4. Bean对象创建流程

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

请添加图片描述

5. lazy-init懒加载机制

AbstractApplicationContext#refresh#finishBeanFactoryInitialization#preInstantiateSingletons#isLazyInit

在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

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

6. 循环依赖问题

循环依赖

在这里插入图片描述

  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个级别的缓存

在这里插入图片描述

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

在这里插入图片描述

  1. 流程图如下
    请添加图片描述
这篇关于SpringIoC 源码深度剖析,先教看源码的方法,然后给出流程图,根据方法和流程图去看,这里会将重点总结出来的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!