文章目录
- 循环依赖
- 缓存
- A-B循环引用
- 总结
循环依赖
对象A中有个属性B
对象B中有个属性A
什么叫不完整对象呢?
对象在创建过程中他存在了两种状态
所以当我们创建好B对象后去给B初始化,但是发现A对象此时是一个半成品对象,所以就会再去创建A对象,导致循环
缓存
这里存在一个问题
如果我持有了某一个对象的引用,那么能否在后续的步骤中给当前对象进行赋值操作?
肯定可以。所以这里就是解决循环依赖的一个思路
当给B对象初始化的时候,直接在容器中拿到A对象的半成品。所以引出了缓存,当我们实例化一个对象之后(不管是否是成品)就放到缓存中
那缓存又是怎么实现的呢?
三个缓存的主要区别在于存的value的类型不一样
那ObjectFactory是什么?
那A-B存在循环引用的时候,它是如何往当前这些缓存里面设置具体值的?
这些操作中最重要的六个方法,带do的方法才是真正干活的方法
A-B循环引用
准备A-B循环引用
直接走到refresh方法finishBeanFactoryInitialization(完成 Bean 工厂初始化)
这个时候的beanFactory还没开始实例化AB对象,在finishBeanFactoryInitialization中才是实例化的关键步骤
往里走,走到preInstantiateSingletons方法,也就是在上面识别FactoryBean的时候见到过
拿到beanNames发现我们的ab对象
当beanName为a的时候就要开始创建我们的A对象了,
走我们的创建逻辑,走到getBean(六个方法中的第一个)
这个getBean就相当于在创建对象A之前先要去判断容器中是否已经创建过此对象
再往下走,doGetBean(六个方法中的第二个)
然后走我们的创建对象逻辑,直接到下面的if逻辑,其中有个getSingleton和上面的不一样,参数不同!
这里面的参数就是labmda
() -> {
try {
return createBean(beanName, mbd, args); // 主要是这个方法
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
那什么时候调用这个方法呢?
在执行getObject的时候才会调用到createBean。这类似于线程中的run方法,在调用start的时候自动调用
往下走进入到getSingleton方法
当我们调用getObeject的时候其实是调用的createBean(六个方法中的第三个),再往下走,就到了createBean
发现createBean中doCreateBean(六个方法中的第四个)方法**,实际创建bean的调用**
再往里走,发现doCreateBean中createBeanInstance(六个方法中的第五个)方法,用来具体的实例化对象,createBeanInstance就是通过反射创建对象
看到我们创建对象是反射之后,在往下走,就回到我们的doCreateBean,看到已经创建好对象A了,A@2615,b=null。此时还没有往缓存中放
还是在doCreateBean中的addSingletonFactory方法,为了避免循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂,这个方法也是三级缓存核心点
- 这个addSingletonFactory方法就是先去一级缓存中判断是否缓存中已经有了,如果还没有。就把beanName(对象a)和一个lambda放到三级缓存中,然后并将beanName从二级缓存中移除,当然这里并没有。
此时我们的A对象已经实例化完毕了,接下来就是初始化a,就是赋值。再回到doCreateBean往下走有个populateBean(六个方法中的第六个)
进到这个populateBean方法走到最后的applyPropertyValues方法,才是基础的赋值操作
进到这个applyPropertyValues方法,跳过中间逻辑,走到getName
- 此时a对象中的b的类型是:RuntimeBeanReference 运行时对象引用
applyPropertyValues中的resolveValueIfNecessary,去处理originalValue(b)
进入到resolveReference方法跳过中间一些获取bean工厂逻辑,走到最重要的点:getBean(第二次看到,第一次在创建A的时候),去获取b
进入getBean,再次见到doGetBean(第二次)
往下走到创建bean实例
然后跟A对象创建是一样的,把B创建出来,B@2953,a=null。
然后就又走到addSingletonFactory方法,往三级缓存中放东西,放的是b和lambda
放好了之后,再走到populateBean,一模一样的逻辑。继续走就会走到getBean(第三次见到),去获取a
走到doGetBean(第三遍这个流程)第一遍获取a,没有创建,给a初始化,去获取b,没有创建,给b初始化,去获取a。
我们进到getSingleton看看
- 先从一级缓存中取,但是现在没有
- 如果一级没有,并且当前对象要都在创建中(我们的ab确实都在创建中,因为还没有初始化完毕),再去二级中取
- 二级没有,并且ab是对象引用,再去三级,但是从三级中取出来的是什么?一个lambda表达式:ObjectFactory<?>
- 当取出lambda之后往下走到getObject,但实际执行的是lambda,也就是:() -> getEarlyBeanReference(beanName, mbd, bean),往下走就到了lambda
这里直接没走到if逻辑,返回的a对象
当我们getObject拿到a对象后
将k=a 和 v=A@2836放到二级缓存中,并将三级缓存中的k=a和v=lambda移除
从三级中移除了a
然后往回走,走到doGetBean,去走其中if逻辑,然后直接返回bean(a)对象,也就是从缓存中取到a对象了
再走到getBean调用处,再来看看beanFactory中的缓存情况
再回到doCreateBean也就是创建b对象的时候,给b初始化的时候,所以走完populateBean之后就赋值完成了
当b初始化完毕的时候,然后一直走,走到一个addSingleton方法,就会把b(成品)放到一级缓存中,并将在二,三级缓存的都清移除
然后继续往回走,走到a对象初始化,populateBean方法后,这个时候a也初始化完成,也会走到addSingleton,放到一级中,并将在二,三级缓存的都清移除
这个时候只是a对象创建完成了,b对象只是在a对象创建的时候创建的,所以现在又回到创建b对象
回到preInstantiateSingletons方法
但是在我们调用getBean的时候走到doGetBean的getSingleton
时候就会从一级中获取到b对象,然后直接返回
这样ab都创建完成了,再来看看流程图
在回顾回顾这六个方法
总结
1、三个缓存对象,在获取数据的时候,是按照什么顺序来获取的?
- 先获取一级缓存,没有在获取二级缓存,没有再获取三级缓存,所以当前面的缓存中存在了对象那么后面就需要把缓存对象给清空
2、如果只有一级缓存,能解决循环依赖问题吗?
- 不能,如果只有一级缓存,那么成品对象和半成品对象会放到一起,这个是没办法区分了,所以要两个缓存来分别存放不同状态的对象,一级缓存放成品,二级缓存放装或品
3、如果只有二个缓存,能否解决循环依赖问题?
- 在刚刚的整个流程中,三级缓存一共出现了几次? 两次。在
getSingleton,doCreateBean
如果对象的创建过程中不包含aop,那么二级缓存就可以解决循环依赖问题,但是包含aop的操作循环依赖问题是解决不了的
会出现异常:This means that said other beans do not use the final version of the bean. 这意味着其他bean不使用bean的最终版本。
4、为什么添加了aop的操作之后就需要添加三级缓存来解决这个问题?
- 三级缓存加了什么操作?
- 添加了一个getEarlyBeanReference的方法
- 在创建代理对象的时候,是否需要生成原始对象?需要
- 当创建完成原始对象之后,后续有需要创建代理对象,那么对象在引用的时候应该使用哪一个?换句话说,就是一个beanName对应有两个对象,(原始对象和代理对象)
- 在整个容器中,有且仅能有一个同名的对象,当需要生成代理对象的时候,就要把代理对象覆盖原始对象
- 程序是怎么知道在什么时候要进行代理对象的创建的呢?
- 需要一个类似于回调的接口判断,当需要第一次对外暴露使用的时候,来判断当前对象是否需要去创建代理对象,而getEarlyBeanRefenerce方法的if判断,就是判断如果需要代理就返回代理对象,如果没有代理就返回原始对象,只能对外暴露一个对象exposedObject
- spring是一个框架跟业务无关,没办法预先感知是否需要代理对象,所以整体的执行流程会把所有可能的情况都考虑进去,因此在存储的时候先要往三级缓存仍一份
5、代理对象创建的时机
- 有循环依赖代理对象在getEarlyBeanRefenerce中创建
- 没有循环依赖的时候创建代理对象的步骤应该放在填充属性之后初始化阶段创建代理对象,也就是在beanPostProcessor里面的after方法里面进行创建
spring只是提供了一个机制来解决循环依赖问题,但是不是百分百解决。