Spring源码项目导入并编译
1、进入gitHub官网
https://github.com/search?q=spring-projects%2Fspring-framework&type=Repositories
2、直接搜索spring-projects/spring-framework
3、选版本
4、点击下载
5、Gradle的安装及本地仓库位置的修改
安装成功
6、导入
7、同步成功就可以了
如果同步不成功,而且编译也报错,则改同步地址,在gradle.build。再次刷新同步
repositories {
///*maven { url "https://repo.spring.io/libs-release" }*/
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
//mavenCentral()
maven { url "https://repo.spring.io/libs-release" }
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/plugins-release" }
mavenLocal()
}
8、然后编译(按一定的顺序)
spring-core——>spring-oxm——>spring-context——>spring-beans——>spring-aspects——>spring-aop——>
需要点他们的测试代码complieTesJava进行编译。
例如:
9、按顺序都编译完后,开始新建一个Module
注意:需要有一个类,这个类呢要注册成bean的对象,要交给ioc容器管理。所以整个工程得依赖spring-context工程。那么在gradle当中,如何去依赖spring-context,
如图所示:需要添加依赖当spring-shanglin这个项目启动的时候,会去调用spring-context的源码
10、创建一个类ShanglinBean并实现处理器
思路:创建一个类ShanglinBean ,让其实现一个特殊的接口,并分别在接口实现的构造器、接口方法中 断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。
同时也创建MyBean的后置处理器,及MyBeanFactory的后置处理器
11、运行后得到如下图所示,表示从官网下载的源码已经能够跑得通
说明源码构建是没有问题的
接下来就是Spring的源码剖析
好处:提高培养代码架构思维、深入理解框架
原则
1、定焦原则:抓主线 (ioc容器初始化主体流程-》beanFactory流程-》bean流程-》懒加载-》Spring ioc循环依赖问题)
2、宏观原则:站在上帝视角,关注源码结构和业务流程(淡化具体某行代码的编写细节)
读源码的方法和技巧
1、断点(观察调用栈)
2、反调(右键 Find Usages 的方式来看看那个地方调用了该方法)
3、经验(spring框架中doXXX,做具体处理的地方)
Spring源码构建
下载源码(github)
1、安装gradle 5.6.3(类似于maven) Idea 2019.1 Jdk 11.0.5
2、导入(耗费一定时间)
3、编译工程(顺序:core-oxm-context-beans-aspects-aop)
工程—>tasks—>compileTestJava
1、Spring IoC容器初始化主体流程
ioc主要有两部分,一个是控制反转(ioc容器帮我们创建所需要的对象),一个是依赖注入(ioc会给我们注入所需对象)
疑问: ioc什么时候帮我们创建好所需的对象,是怎么创建的,放到哪里去了呢,
又是如何进行依赖注入的呢?
带着ioc这些疑问来看ioc的源码,可能思路会更加清晰。
Spring IoC的容器体系
IoC容器是Spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从的一套原则,具体的容器实现可以增加额外的功能,比如我们常用到的ApplicationContext,其下更具体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等一系列的内容,AnnotationConfigApplicationContext 则是包含了注解解析等一系列的内容。Spring IoC 容器继承体系非常优雅,需要使用哪个层次就用哪个层次即可,不必使用功能大而全的。
/**
* Ioc 容器源码分析基础案例
*/
@Test
public void testIoC() {
// ApplicationContext是容器的高级接口,BeanFacotry(顶级容器/根容器,规范了/定义了容器的基础行为)
// Spring应用上下文,官方称之为 IoC容器(错误的认识:容器就是map而已;准确来说,map是ioc容器的一个成员,
// 叫做单例池, singletonObjects,容器是一组组件和过程的集合,包括BeanFactory、单例池、BeanPostProcessor等以及之间的协作流程)
/**
* Ioc容器创建管理Bean对象的,Spring Bean是有生命周期的
* 构造器执行、初始化方法执行、Bean后置处理器的before/after方法、
* :AbstractApplicationContext#refresh#finishBeanFactoryInitialization
* Bean工厂 后置处理器初始化、方法执行
* :AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors
* Bean 后置处理器初始化
* :AbstractApplicationContext#refresh#registerBeanPostProcessors
*/
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
System.out.println(lagouBean);
}
BeanFactory根接口(顶级接口)
getBean是一个基础行为,别人才能去调用,而且采用了多态的方法
BeanFactory容器继承体系
层级划分分析的清晰明确,非常的好,想拥有什么功能就直接去继承就可以了。不需要把所有的接口方法都去实现。只要实现想拥有的功能,就去实现对应的接口就可以了。
2、IoC容器初始化主体流程之Bean周期关键时机点代码调用分析
ioc是用来管理和创建bean对象的,而bean对象在形成过程中又有那么多的特殊时间点,那么通过断点来查看这些特殊时间点在框架的那个地方进行调用。那么抽取几个进行分析
Bean生命周期关键时机点
目前分析步骤如下:
1、Bean对象的构造器是何时调用,
2、做初始化方法实现,看初始化方法被底层什么方法、何时调用
3、自定义的beanFactory后置处理器何时调用
4、自定义的bean后置处理器何时去初始化&何时调用。
接下来通过打断点去分析,当执行到断点处则会停下来。那么直接看调用栈信息
(给无参构造方法打断点,是看何时被初始化。
给bean里面具体的方法打断点,是看何时被调用)
1、构造器何时调用&初始化
1) 对构造方法打断点并Debug启动(看何时被初始化)
先对构造函数(无参构造)打断点。
当Debug启动后,如果代码的执行被停到断点处,说明此构造器已经被调用。
2) 通过Debug模式查看调用栈信息
上面代码已经停到断点处,那么通过Debug来看何时进行初始化,
查找发现在refresh里面又调用finishBeanFactoryInitialization方法。
说明构造器的初始化方法执行是在此方法里面。
3) 给无参构造里面具体方法打断点并启动Debug(看何时被调用)
4) 通过Debug模式查看调用栈信息
通过同样的方式查看初始化方法,来判断构造器何时被调用,
先到初始化方法里面打断点,Debug模式查看调用栈信息。发现结论和上面是一样.
得出结论:通过如上观察,我们发现构造函数的初始化/调用执行的时机在AbstractApplicationContext类refresh方法的 finishBeanFactoryInitialization(beanFactory)处;
构造器执行、初始化方法执行
:AbstractApplicationContext#refresh#finishBeanFactoryInitialization
2、BeanFactory后置处理器何时调用&初始化
1) 需要先在xml中对后置处理器注册成一个Bean
2) 对BeanFactory后置处理器打断点。
当代码停到断点处,说明后置处理器已经被调用
3) 通过Debug模式查看调用栈信息
查看调用栈信息,找出它的初始化在哪里。
BeanFactory后置处理器初始化时间点查看结束。
4) 对BeanFactory的方法打断点并启动Debug
对Bean工厂里面的方法打断点,看何时去调用Bean工厂里面的方法的
5) 通过Debug模式查看调用栈信息
查看调用栈信息,找出它的BeanFactory调用方法的时机点在哪里。
BeanFactory后置处理器调用时间点查看结束。
得出结论: Bean工厂 后置处理器初始化、方法执行
:AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors
3、Bean的后置处理器何时调用&初始化
1) 对Bean后置处理器构造方法打断点。
当代码停到断点处,说明bean的后置处理器已经被调用
2) 通过Debug模式查看调用栈信息
查看调用栈信息,找出它的初始化时机点在哪里。
Bean后置处理器初始化时间点查看结束。
3) 对Bean后置处理器里面的方法打断点并Debug启动
4) 通过Debug模式查看调用栈信息
查看调用栈信息,找出它何时执行。
得出结论:
* Bean 后置处理器初始化
:AbstractApplicationContext#refresh#registerBeanPostProcessors
Bean后置处理器的before/after方法执行时机点
:AbstractApplicationContext#refresh#finishBeanFactoryInitialization
最终的总结论如下:
调试栏
通过上面的断点,发现多次调用到refresh这个方法里面,所以这个方法对于Spring IoC 容器初始化来说应该相当关键, 是很多的重要执行过程。完成了初始化的很多动作。
关键点 | 触发代码 |
---|---|
构造器 | refresh#finishBeanFactoryInitialization(beanFactory) |
(beanFactory) BeanFactoryPostProcessor 初始化 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanFactoryPostProcessor 方法调用 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanPostProcessor 初始化 | registerBeanPostProcessors(beanFactory) |
BeanPostProcessor 方法调用 | refresh#finishBeanFactoryInitialization(beanFactory) |
3、IoC容器初始化主体流程之refresh方法
由上分析可知,通过断点查看发现其规律,都锁定到一个方法,Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() 方法中 ,我们查看 refresh 方法来俯瞰容器创建的主体流程,主体流程下的具体子流程我们后面再来讨论
分析refresh方法
通过断点进行调试
1、先打一个断点,debug启动
因为这句代码就是获取IOC容器的,那么它肯定会进行容器的初始化。
2、代码停到断点处后,进入到构造函数中
因为打断点那句代码是一个构造函数,(那么这个构造函数是怎么进行容器初始化的呢,具体的详情只能是进入代码里面才能晓得了。)
3、跳出静态代码块
4、再次进入,就进入到了它的构造器
通过this调用当前类的重载方法,需要继续进入构造器
5、因为构造器调用另一个构造,还需继续进入
发现调用父类,而且还有其他的动作。如果平行走就到第6点了。
6、对配置文件的解析,进去再出来。
setconfigLocations是对配置文件进行解析的,可以对多个配置文件进行解析处理。
7、重点:显示一个true,进入到refresh
(因为refresh完成了ioc容器启动的绝大多数工作,也就是说它是ioc容器启动的一个核心方法)
为何叫refresh,因为ioc给我们提供了一个接口,当一个容器启动之后,还可以调用refresh刷新容器。
初始化也相当于一个刷新吧。 下一步继续进入吧,看看如何初始化
8、refresh方法里面为什么要用对象锁
右键,通过反调查看为什么要用对象锁,可以发现,
如果容器在启动或者刷新的过程中,必须得锁住,不能进行如close操作吧,
要close也得我初始化完释放锁(不用的时候),才能进行关闭吧。
启动/close不能同时执行,只能等一个执行完先。这就是锁的目的所在。
refresh的其他具体细节得查看源码
9、继续的核心代码如下: 步骤为
a、先创建工厂。
b、初始化、以及执行工厂的后置处理器。
c、注册Bean的后置处理器。
d、实例化、初始化bean。bean有了就调用bean的一系列方法。。。
e、直至整个初始化完成,bean对象产生
先创建一个工厂——》对工厂初始化,&执行工厂的后置处理器——》注册bean的后置处理器——》实例化,初始化bean——》调用bean的后置处理器。直至整个初始化完成,bean也就完成了。这是主体的一个流程。
上面主要的工作是在refresh完成。
4、BeanFactory创建流程剖析
1、获取BeanFactory子流程剖析
1、把之前的那个断点取消掉,因为需要分析BeanFactory子流程,所以要重新在refresh方法里面的创建BeanFactory的地方打断点。当debug模式停到打断点出时,说明已经开始 要 创建BeanFactory 了。
那么具体如何创建,点击下一步,看里面是如何创建的嘛!
2、进来发现又继续调用refreshBeanFactory方法说明BeanFactory已经产生
那么进入到这个方法里面,看看里面是如何生成BeanFactory
3、在refreshBeanFactory方法中进行创建BeanFactory准备工作
先判断是否存在工厂,如果有就销毁并关闭。如果没有就开始实例化创建BeanFactory。
具体如下所示:(里面是具体如何创建BeanFactory的及如何设置属性的)
4、真正创建工厂是调用创建工厂的方法createBeanFactory。
5、将创建好的工厂取出
上面已经将beanFactory创建好了,并放到了beanDefinitions中,
和第2点相对应嘛,上面是创建,下面是取。
所以第一步是有返回值的,返回值就是一个beanFactory
到此,beanfactory的子流程也就出来了。
画时序图如下
其实做架构的时候是经常用的工具。(两条生命线)
2、BeanDefinition加载解析及注册子流程
(1)该子流程涉及到如下几个关键步骤:
1) Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML文件, 并将其封装成Resource对象。
2) BeanDefinition载入:把用户定义好的Javabean表示为loC容器内部的数据结构, 这个容器内部的数据结构就是BeanDefinition。
3) 注册BeanDefinition到loC容器
所谓的注册就是把 XML 中定义的 Bean信息封装为 BeanDefinition 对象之后 放个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition 的。
1、在refreshBeanFactory方法的加载BeanDefinitions打断点
因为要了解BeanDefinitions,就得晓得他是如何加载的。所以在此处打上断点,当代码运行停到此处,说明要调用这里了。通过下一步,进入方法里面,查看并找到加载BeanDefinition进行注册的地方。
2、创建读取器并设置属性
创建XmlBeanDefinitionReader(读取器),并给读取器设置一些属性信息
到loadBeanDefinitions,则是去加载真正的BeanDefinition信息。进行进入里面,看看如何实现
3、通过两种方式对BeanDefinition进行加载
如果configResources、configLocations不为空就加载进来。当前是用xml形式,那么肯定走下面那个咯
4、累计加载BeanDefinition的个数
那么BeanDefinition又是从哪里来呢,不然它怎么能累计呢,其实是通过loadBeanDefinitions返回的。
那么真正的加载BeanDefinition肯定是在这个方法里面的。继续进入
AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String…)
5、这里是个接口,那么它得调用它的实现类进行操作
点击进行进入,进入到它的实现类
6、读取资源信息,转为BeanDefinitions
但是发现读取信息转为Resource后,将Resource传给 loadBeanDefinitions(resources)重载方法;
那么BeanDefinitions的封装应该是在这个里面,继续进入重载方法
7、这个和第四点的重载方法一样,但是参数变了。
这里是对资源的循环,第四点是对xml的循环,继续进入重载方法
AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource…)
8、上面进入是一个接口,继续进入接口的实现类,就到此处
将得到的资源转为输入流,然后通过doLoadBeanDefinitions对流进行解析处理
doXXX 是真正干活的地方,进入
9、解析xml文件并进行封装注册
将资源(xml)和流封装成document对象,将document进行解析并封装注册。
其实是将xml中的每一个bean封装成一个BeanDefinition,并注册到Map集合中
那么继续进入,看里面是如何进行注册的。
到此,读取xml资源为document已经完成。
这里是接口,具体的实现肯定是通过实现类的,所以手动就点进去。如果是debug模式,就直接到下面。不用点
10、doXXX开头的是真正做事情的
那么继续点进去,看它如何进行注册的
11、里面通过调用委托实现(就是给另一个类进行处理)
前置、后置这些方法里面都是空的,都是供子类去实现
它又继续调用parseBeanDefinitions对资源进行处理
12、对xml标签信息继续解析
肯定是对xml的默认的标签信息进行解析了
13、import元素处理、alias元素处理、bean元素处理、嵌套beans元素处理、
14、这里又发现调用registerBeanDefinition
在这个方法里面并没有立刻封装BeanDefinition对象,而是封装BeanDefinitionHolder,因为它里面有BeanDefinition对象。那么有了这个对象,又是如何进行封装的呢。继续看如何实现注册
15、将中的bean的name取出,放到registry中
其实就是将bean标签的id作为key,整个bean作为value,存到一个map集合中
这个集合是 在DefaultListableBeanFactory实现类里面的 beanDefinitionMap
16、调用具体的map进行放信息
//仍处于启动注册阶段,还没有进行实例化
BeanFactory创建的子流程调用栈时序图
bean对象的创建流程和Spring创建流程是一样的。
5、Bean对象创建流程
其实这节的流程听得不太明白。
1、从finishBeanFactoryInitialization方法打断点,当断点停在此处则为已经调用
通过最开始的关键时机点分析,我们知道Bean创建子流程入口在AbstractApplicationContext#refresh()方法的finishBeanFactoryInitialization(beanFactory) 处
2、进入finishBeanFactoryInitialization看里面如何初始化
如果的手动的点进入则如下:
——————————start————————————-
AbstractApplicationContext#finishBeanFactoryInitialization方法也是继续调用
调用实现类
——————————end————————————-
手动点击就是如此,如果是通过debug模式,则直接进入第3点
3、在preInstantiateSingletons进行单例bean的实例化
在DefaultListableBeanFactory类的preInstantiateSingletons方法,
可以找到下面部分的代码,看到工厂Bean或者普通Bean,最终都是通过getBean的方法获取实例
//触发所有立即加载 (非懒加载)单例bean的初始化,主要步骤是getBean
如果是手动,是从接口进入的。doXXX,也就是真正做事情的
4、根据beanName实例化并获取bean对象
继续跟踪下去,我们进入到了AbstractBeanFactory类的doGetBean方法,
这个方法中的代码很多,可直接找到核心部分
5、进入到createBean方法里面看如何创建bean实例的
手动点击的,就按Ctrl进入。它就一个实现类。
6、进入方法里面看上面的注释就晓得它的功能
AbstractAutowireCapableBeanFactory#createBean()
7、在createBean方法里面继续进入doCreateBean方法
AbstractAutowireCapableBeanFactory#createBean()
8、 进入doCreateBean方法看看
在该方法需要关注两块重点区域
a、创建Bean实例,调用无参构造,此时尚未设置属性
可以看打印:(也是生命周期的第一步)
b、给Bean对象填充属性,调用初始化方法,并应用BeanPostProcessor后置处理器
9、通过createBeanInstance进行看是如何创建bean实例
10、看看里面如何实例化initializeBean
11、真正实例化bean
15、调用后置处理器方法遍历所有后置处理器
到此,bean对象创建完成。(容器启动时候会判断是否是单例、是否是立即加载的)
6、lazy-init 延迟加载机制原理
lazy-init 延迟加载机制分析
普通 Bean 的初始化是在容器启动初始化阶段执行的,而被lazy-init=true修饰的 bean 则是在从容器里第一次进行context.getBean() 时进行触发。
Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化时用,然后对每个 BeanDefinition 进行处理,
如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进行初始化并依赖注入。
1、如果设置懒加载,第一次调用bean时候去创建剖析
到此步骤就和上面的第三点是一样的。
public void preInstantiateSingletons()throws BeansException{
// 所有beanDefinition集合
List<String> beanNames=new ArrayList<String>(this.beanDefinitionNames);
// 触发所有懒加载单例bean的初始化
for(String beanName:beanNames){
// 获取bean 定义
RootBeanDefinition bd=getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if(!bd.isAbstract()&&bd.isSingleton()&&!bd.isLazyInit()){
// 判断是否是 FactoryBean
if(isFactoryBean(beanName)){
final FactoryBean<?> factory=(FactoryBean<?>)
getBean(FACTORY_BEAN_PREFIX+beanName);
boolean isEagerInit;
if(System.getSecurityManager()!=null&&factory instanceof SmartFactoryBean){
isEagerInit=AccessController.doPrivileged(new PrivilegedAction<Boolean>(){
@Override
public Boolean run(){
return((SmartFactoryBean<?>)factory).isEagerInit();
}
},getAccessControlContext());
}
}else{
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和懒加载时 context.getBean("beanName") 所触发的逻辑是一样的
*/
getBean(beanName);
}
}
}
}
总结
对于被修饰为lazy-init的bean Spring容器初始化阶段不会进行 init 并且依赖注入,当第一次进行getBean时候才进行初始化并依赖注入
对于非懒加载的bean,getBean的时候会从缓存里头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来
7、Spring IoC循环依赖问题
7.1 什么是循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。比如A 依赖于B,B依赖于C,C又依赖于A。
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件.
Spring中循环依赖场景有:(依赖就是注入)
构造器的循环依赖(构造器注入)
Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决 属性循环依赖时,spring采用映射 用的是提前暴露对象的方法。
7.2 循环依赖处理机制
单例 bean 构造器参数循环依赖(无法解决)
构造函数之所以不能解决循环依赖注入的问题,是因为构造函数根本没有创建实例化对象,那么三级缓存就没有实例化对象,何谈解决呢?所以只有Set方法来解决。
prototype 原型 bean循环依赖(无法解决)
因为创建了对象后不再容器中管理的,是脱离状态的对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依赖,Spring都会直接报错处理。
AbstractBeanFactory
.doGetBean()方法:
总结:Spring 不支持原型 bean 的循环依赖。
单例bean通过setXxx或者@Autowired进行循环依赖
下图就是互相依赖,互相注入给对方
Spring的循环依赖的理论依据基于Java的引用传递, 当获得对象的引用时, 对象的属性是可以延后设置的,但是构造器必须是在获取引用之前
Spring通过set Xxx或者@Autowired方法解决循环依赖
其实是通过提前暴露一个Object Factory对象来完成的, 简单来说Class A在调用构造器完成对象初始化之后, 在调用Class A的set Class B方法之前就把Class A实例化的对象通过Object Factory提前暴露到Spring容器中。
- Spring容器初始化Class A通过构造器初始化对象后提前暴露到Spring容器。o Class A调用set Class B方法, Spring首先尝试从容器中获取Class B, 此时Class B不存在Spring容器中。
- Spring容器初始化Class B, 同时也会将Class B提前暴露到Spring容器中
Class B调用set Class A方法, Spring从容器中获取Class A, 因为第一步中已经提前暴露了
Class A, 因此可以获取到Class A实例
这样Class A和Class B都完成了对象初始化操作, 解决了循环依赖问题。
- ”Class A通过spring容器获取到Class B, 完成了对象初始化操作。
理论上如何解决:
1、A创建的过程中发现依赖于B,但是此时B还没有创建。那么A肯定没有完全的创建好,此时就形成了一个闭环,那么Spring用的就是三级缓存来解决这个问题。(实例化后放入三级缓存就是为了提前暴露自己))
根据SpringBean的生命周期而言,只是完成了实例化,也就是第一步,是一个没有长大的Bean
2、A放入三级缓存后,发现依赖B。那么此时B就要去创建了。创建过程中发现又依赖于A。
3、此时B会先去三级缓存中去拿出尚未成熟的A放入到二级缓存当中。(A虽然是一个尚未成为一个完整的SpringBean,但此时A已经是一个对象了。根据Java的对象引用机制,它是可以引用的。虽然属性值还没有)
为什么要三级呢,二级不就可以了吗?是因为从三级到二级过程中可以做一些扩展操作。所以需要三级缓存。
A从三级到二级,已经做了某些扩展,及使用。此时B有了A,那么B就可以进行SpringBean的创建了,等B完成创建之后。
4、B会将自己放到一级缓存中。
5、那么此时A可以使用一级缓存当中的B。然后就可以进行完整创建A了。
源码剖析如下:
DefaultListableBeanFactory#preInstantiateSingletons
注意:此处开始是重点了(第一次进来肯定是获取不到另一个bean对象的,因为还没有创建)
一路走下来,调用createBean创建bean对象
AbstractAutowireCapableBeanFactory#createBean()
调用doCreateBean去创建真正的bean对象
在doCreateBean方法里面创建实例(并判断是否提前暴露)
将第一个bean对象创建出来并放到三级缓存中
DefaultSingletonBeanRegistry#addSingletonFactory
然后跳出来。继续为bean对象进行设置属性
处理属性的循环依赖
AbstractAutowireCapableBeanFactory#populateBean
调用重载方法
到这里的getBean和之前的是一样的
到此,Bean a 已经放入三级缓存
==================》》
那就得去调用获取bean b拉,调用doGetBean方法。
先判断是否有bean b
第一次取,只能是从三级缓存获取,并放到二级缓存
从二级缓存取出后就直接返回
然后返回。实例化bean b(是存到二级缓存的)
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
因为beanB已经被封装好了
使用三级缓存解决了Spring的循环依赖问题