作者:半分、\
起源:https://www.cnblogs.com/semi-...
前言
在应用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring曾经帮咱们去解决循环依赖问题,对咱们开发者来说是无感知的,上面具体分析一下spring是如何解决bean之间循环依赖,为什么要应用到三级缓存,而不是二级缓存
bean生命周期
首先大家须要理解一下bean在spring中的生命周期,bean在spring的加载流程,才可能更加清晰晓得spring是如何解决循环依赖的
咱们在spring的BeanFactory工厂列举了很多接口,代表着bean的生命周期,咱们次要记住的是我圈红线圈进去的接口, 再联合spring的源码来看这些接口次要是在哪里调用的
AbstractAutowireCapableBeanFactory类的doCreateBean办法是创立bean的开始,咱们能够看到首先须要实例化这个bean,也就是在堆中开拓一块内存空间给这个对象,createBeanInstance办法外面逻辑大略就是采纳反射生成实例对象,进行到这里示意对象还并未进行属性的填充,也就是@Autowired注解的属性还未失去注入
咱们能够看到第二步就是填充bean的成员属性,populateBean办法外面的逻辑大抵就是对应用到了注入属性的注解就会进行注入,如果在注入的过程发现注入的对象还没生成,则会跑去生产要注入的对象,第三步就是调用initializeBean办法初始化bean,也就是调用咱们上述所提到的接口
能够看到initializeBean办法中,首先调用的是应用的Aware接口的办法,咱们具体看一下invokeAwareMethods办法中会调用Aware接口的那些办法
咱们能够晓得如果咱们实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware三个Aware接口的话,会顺次调用setBeanName(), setBeanClassLoader(), setBeanFactory()办法,再看applyBeanPostProcessorsBeforeInitialization源码
发现会如果有类实现了BeanPostProcessor接口,就会执行postProcessBeforeInitialization办法,这里须要留神的是:如果多个类实现BeanPostProcessor接口,那么多个实现类都会执行postProcessBeforeInitialization办法,能够看到是for循环顺次执行的,还有一个留神的点就是如果加载A类到spring容器中,A类也重写了BeanPostProcessor接口的postProcessBeforeInitialization办法,这时要留神A类的postProcessBeforeInitialization办法并不会失去执行,因为A类还未加载实现,还未齐全放到spring的singletonObjects一级缓存中。
再看一个留神的点
能够看到ApplicationContextAwareProcessor也实现了BeanPostProcessor接口,重写了postProcessBeforeInitialization办法,办法外面并调用了invokeAwareInterfaces办法,而invokeAwareInterfaces办法也写着如果实现了泛滥的Aware接口,则会顺次执行相应的办法,值得注意的是ApplicationContextAware接口的setApplicationContext办法,再看一下invokeInitMethods源码
发现如果实现了InitializingBean接口,重写了afterPropertiesSet办法,则会调用afterPropertiesSet办法,最初还会调用是否指定了init-method,能够通过<bean init-method>标签,或者@Bean注解的initMethod指定,最初再看一张applyBeanPostProcessorsAfterInitialization源码图
发现跟之前的postProcessBeforeInitialization办法相似,也是循环遍历实现了BeanPostProcessor的接口实现类,执行postProcessAfterInitialization办法。整个bean的生命执行流程就如下面截图所示,哪个接口的办法在哪里被调用,办法的执行流程
最初,对bean的生命流程进行一个流程图的总结
三级缓存解决循环依赖
上一大节对bean的生命周期做了一个整体的流程剖析,对spring如何去解决循环依赖的很有帮忙。后面咱们剖析到填充属性时,如果发现属性还未在spring中生成,则会跑去生成属性对象实例
咱们能够看到填充属性的时候,spring会提前将曾经实例化的bean通过ObjectFactory半成品裸露进来,为什么称为半成品是因为这时候的bean对象实例化,然而未进行属性填充,是一个不残缺的bean实例对象
spring利用singletonObjects, earlySingletonObjects, singletonFactories三级缓存去解决的,所说的缓存其实也就是三个Map
能够看到三级缓存各自保留的对象,这里重点关注二级缓存earlySingletonObjects和三级缓存singletonFactory,一级缓存能够进行疏忽。后面咱们讲过先实例化的bean会通过ObjectFactory半成品提前裸露在三级缓存中
singletonFactory是传入的一个匿名外部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference办法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。
咱们假如当初有这样的场景AService依赖BService,BService依赖AService
- AService首先实例化,实例化通过ObjectFactory半成品裸露在三级缓存中
- 填充属性BService,发现BService还未进行过加载,就会先去加载BService
- 再加载BService的过程中,实例化,也通过ObjectFactory半成品裸露在三级缓存
- 填充属性AService的时候,这时候可能从三级缓存中拿到半成品的ObjectFactory
拿到ObjectFactory对象后,调用ObjectFactory.getObject()办法最终会调用getEarlyBeanReference()办法,getEarlyBeanReference这个办法次要逻辑大略形容下如果bean被AOP切面代理则返回的是beanProxy对象,如果未被代理则返回的是原bean实例,这时咱们会发现可能拿到bean实例(属性未填充),而后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化实现后,A会持续进行后续的初始化操作,最终B会注入的是一个残缺的A实例,因为在内存中它们是同一个对象。上面是重点,咱们发现这个二级缓存如同显得有点多余,如同能够去掉,只须要一级和三级缓存也能够做到解决循环依赖的问题???
只有两个缓存的确能够做到解决循环依赖的问题,然而有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只应用两个缓存是无奈解决问题,上面来看一下bean被AOP进行了切面代理的场景
咱们发现AService的testAopProxy被AOP代理了,看看传入的匿名外部类的getEarlyBeanReference返回的是什么对象
发现singletonFactory.getObject()返回的是一个AService的代理对象,还是被CGLIB代理的。再看一张再执行一遍singletonFactory.getObject()返回的是否是同一个AService的代理对象
咱们会发现再执行一遍singleFactory.getObject()办法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singleFactory.getObject()办法又会产生新的代理对象,假如这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()办法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里咱们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,前面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()办法再产生一个新的代理对象,保障始终只有一个代理对象。还有一个留神的点
既然singleFactory.getObject()返回的是代理对象,那么注入的也应该是代理对象,咱们能够看到注入的的确是通过CGLIB代理的AService对象。所以如果没有AOP的话的确能够两级缓存就能够解决循环依赖的问题,如果加上AOP,两级缓存是无奈解决的,不可能每次执行singleFactory.getObject()办法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保留产生的代理对象
总结
后面先讲到bean的加载流程,理解了bean加载流程对spring如何解决循环依赖的问题很有帮忙,前面再剖析到spring为什么须要利用到三级缓存解决循环依赖问题,而不是二级缓存。网上能够试试AOP的情景,实际一下就能明确二级缓存为什么解决不了AOP代理的场景了
在工作中,始终认为编程代码不是最重要的,重要的是在工作中所养成的编程思维。
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.20w 程序员红包封面,快快支付。。。
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!