spring循环依赖看不懂callme
2024-02-02 20:21:53 0 举报
在Spring框架中,循环依赖是一个常见的问题。当两个或多个bean之间存在直接或间接的依赖关系,且这些依赖关系形成了一个闭环,就称为循环依赖。spring框架默认支持循环依赖的解决,主要是通过三级缓存singletonFactories和earlySingletonObjects来实现。以下是一个循环依赖的例子: 假设有两个bean:A和B,其中A依赖于B,B也依赖于A。如果我们先创建A,然后在A的构造函数中需要B,那么Spring框架会首先尝试从单例池中获取B。如果在创建B之前尚未创建,那么Spring框架会创建一个临时的B对象,并放入到earlySingletonObjects缓存中,然后继续创建A。此时,A已经完成了创建,Spring框架会将其放入到singletonFactories缓存中。最后,B被创建并完成初始化,然后放入到单例池中,并且从earlySingletonObjects缓存中移除临时对象。这就是spring框架解决循环依赖的基本过程。
作者其他创作
大纲/内容
如果一级缓存不包含当前beanNameif (!this.singletonObjects.containsKey(beanName)) {
参数说明:1、beanName2、font color=\"#7bd144\
不为空--报错
afterSingletonCreation(beanName);
引申出来
bean名称已经注入到其他bean中actualDependentBeans集合 在原始版本中是循环引用的一部分,但最终还是变成了循环引用包装。这意味着其他bean不使用“+”的最终版本bean。这通常是过于急于进行类型匹配的结果——可以考虑使用 例如,关闭 allowEagerInit 标志的 getBeanNamesForType 。
InstantiationAwareBeanPostProcessor#BeanPostProcessorsBeforeInstantiation#BeanPostProcessorsAfterInitialization
getEarlyBeanReference 函数式方法为了防止循环依赖的方法,如果没有循环依赖,就不会执行
调用所有的BeanPostProcessors,执行方法postProcessBeforeInitializationeg:ApplicationContextAwareProcessorImportAwareBeanPostProcessorBeanValidationPostProcessor
中间很多操作略过
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
finishBeanFactoryInitialization(beanFactory);
否
既然是if判断,就肯定有这样的情况发生,比如@Async注解,如果存在循环依赖就会出现exposedObject和 bean不相等的情况,也就是exposedObject被@Async注解的解析器prossor代理了一层,那么就要走spring的审判了,给出具体的审判的报错理由,下面写的很明确,继续看
启动一个spring的容器eg;ApplicationContext context = new ClassPathXmlApplicationContext(\"circle-aop.xml\");
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation#postProcessPropertyValues
refresh()的方法里面
SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
调用所有的BeanPostProcessors,执行方法postProcessAfterInitializationeg:BeanPostProcessorCheckerAspectJAwareAdvisorAutoProxyCreatorApplicationListenerDetector感觉类似aware,需要实现它才能具备里面的方法的能力
会有机会获取引用对象
内部方法,里面再调用函数式方法
移除到当前正常创建bean的集合中singletonsCurrentlyInCreation
BeanPostProcessor及子接口
如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则”这里的设计原则是“延迟实例化”,Spring容器尽可能地推迟Bean的实例化,直到该Bean被真正需要时才进行实例化。这样可以提高应用程序的性能和资源利用率,并且避免不必要的开销,创建代理按延迟实例化原则一般是尽量在初始化后摘录:https://segmentfault.com/a/1190000023647227
第二个参数
true
开始遍历上面得到的依赖当前bean的集合,这个逻辑比较绕,一般也不会进入首先看this.alreadyCreated这个遍历,是否包含当前遍历的bean的名称1、包含,证明容器的确有这个bean,并且是依赖当前主bean的,那么就存在一种这个bean,依赖的主bean的版本不是最终的version的情况actualDependentBeans.add(dependentBean);2、不包含,证明虽然有个名称的bean,的确依赖了当前主bean,但是还没创建,或者不需要创建,那么就删除一下?为什么删除,我的理解是,这个bean既没创建过,也可能不需要创建,为了保险起见,就彻底从容器中删除啊removeSingleton(beanName);
内逻辑
判断是否实现InitializingBean接口,并执行afterPropertiesSet方法
是
false
判断bean不为空
BeanPostProcessors#postProcessBeforeInitialization#PostProcessorsAfterInitialization
Spring Boot 2.6.0正式发布,循环引用终于被禁1、只是Spring Boot默认禁止了,但Spring Framework默认还是允许的哦2、对于有代码洁癖的开发者来说,看到循环引用的代码是“不舒服”的。在业务开发中,有一种声音是:循环引用不可避免,但实际上应该思考:若出现了循环引用,必定是结构设计上不合理导致,有优化空间!若你是个有追求的程序员,是可以很容易发现这种不合理的。3、循环引用属于不合理的设计,但并非不能正常工作。这就像每个程序员都吐槽过屎山代码依旧能正常work同一个道理:它不好,但有意义。既然“不合理”,那就有理由规避。针对循环引用的解决方案,总结一下主要有两种:①. 确保循环引用不再存在:整改/优化业务逻辑②. 允许循环引用:无需改代码4、springboot开启循环依赖的开关,想到这里,联想到spring里面有个配置,就是允许循环依赖的配置,springboot也是借助这个配置做到文章,如下方式①在配置文件application.properties里加上这个属性spring.main.allow-circular-references = true②通过启动类API的方式来设置,能达到同样效果:public static void main(String[] args) { new SpringApplicationBuilder(Application.class) .allowCircularReferences(true) // 允许循环引用 .run(args);}5、@Async和@lazy注解循环依赖问题,自己去探索
抛错误
如果一级缓存为空,并且isSingletonCurrentlyInCreation里面包含这个beanName
beforeSingletonCreation(beanName);
doGetBean()
接口SmartInitializingSingleton回调处理
判断三级缓存是否为null
内部逻辑比较重要,看到do开头的一般都重点看
判断inCreationCheckExclusions是否包含beanName
逻辑解释:和上面的put的方法首尾呼应这个地方是bean基本上生命的最后的位置执行的代码分两种情况两个场景1、只有是容器存在AbstractAutoProxyCreator这样的BeanPostProcessor的时候2、如果不存在,则会进入,因为可能上面没有触发被动技能,需要这里主动尝试的去看看,到底是否需要获取一个被代理的对象则wrapIfNecessary方法返回代理对象。否则,走个过场,返回原始对象3、如果存在,则earlyProxyReferences.remove执行后,可以返回bean对象,并且和当前bean相等,证明之前put过,就是被别的bean捞过一次,这好像是触发了bean的被动技能,这个方法一执行,就会把当前bean的早期引用给暴露出来,放到三级缓存
报错之前做了一个判断。很关键else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
其他准备工作略过
先查询一级缓存Object singletonObject = this.singletonObjects.get(beanName);
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
先查一级缓存Object singletonObject = this.singletonObjects.get(beanName);
return singletonObject;
return exposedObject;
applyBeanPostProcessorsAfterInitialization
判断根据类型或者根据名称Autowire注入xml依赖对象
命运齿轮开始转动
if (exposedObject == bean) {font color=\"#4ccbcd\
直接返回bean
不为空
上面华丽胡哨的前夕都做完了,善始善终,最后这个判断很关键,再次揭秘一下循环依赖的内幕
执行实现这个接口的方法InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation目前源码没实现这个方法
Bean不是通过属性或构造函数参数显式依赖于另一个Bean的,但是却需要在创建一个Bean对象之前,需要先创建另一个Bean对象,此时就可以使用@DependsOn注解
PUT三级
return AOPBean
是否为空
spring容器的DefaultSingletonBeanRegistry方法存在两个这样的同名方法,但是里面逻辑不一样第一个getSingleton方法说明
AbstractAutoProxyCreator#postProcessAfterInitialization这个方法是生成代理对象的核心常规位置
结论:1、普通的单例bean生成,先放三级缓存,然后最后放到一级缓存2、需要被AOP代理的bean生成,先放三级缓存,然后放一级缓存3、A和B互相依赖的情况和其他情况自己脑补todo
从这里开始遍历之前历经各种处理生成的beanDefinitionNames
继续引申
判断当前工厂是不是正在销毁单例bean
函数式方法被调用时机
执行实现这个接口的方法InstantiationAwareBeanPostProcessor#postProcessPropertyValues
把当前bean名称记录到已经注册的集合中this.registeredSingletons.add(beanName);
都会有机会调用getBean方法
往上看,注意第二个参数false
开始createBean
AOP生成代理的方法,里面会判断当前bean能否获取到拦截器链,如果为空就不处理font color=\"#f20ef2\
这个是bean工厂准备阶段可以控制这个是否允许循环依赖的控制代码protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences); } }
标记当前bean 是已经创建alreadyCreated
doCreateBean()
吐出的对象地址一般类似Student$$EnhancerBySpringCGLIB$$276e118a@2409
实例化bean的方法,几种方式,1、通过Supplier实例化2、通过工厂方法实例化3、用合适的构造函数实例化4、使用默认构造函数构造--调用无参构造函数
为空
参数说明:1、beanName2、RootBeanDefinition mbd3、Object bean
不会出现循环依赖问题getBean()
只有一、三级缓存不为空,并且allowEarlyReference为true的情况才会有机会执行put二级缓存
背景介绍:1、通过初始化一个简单的bean,了解一个bean的生成流程2、通过初始化两个有依赖关系的bean,了解简单循环依赖的流程3、通过初始化一个简单的bean,并且被AOP代理的bean的生成流程。4、通过初始化两个有依赖关系,并且都被AOP代理的bean的生成流程
得到一个font color=\"#e74f4c\
报错!!!!
return bean
hasDependentBean(beanName)内部return this.dependentBeanMap.containsKey(beanName);
为什么留大白。留给你写呗.
3、移除对应三级缓存this.singletonFactories.remove(beanName);
allowRawInjectionDespiteWrapping尽管有包装,还是允许注入这是一个配置,表示 容器内的一个bean, 尽管这个bean可能被AOP代理,还是允许别的bean 注入这个bean的原始对象。
判断actualDependentBeans是否为空。就是当前实际依赖主bean的对象集合,也就是被骗的对象们
beanFactory.preInstantiateSingletons()
如果二级缓存没有,并且font color=\"#7bd144\
populateBean()
代理对象生成逻辑见名知意,获取早期的bean的引用。判断是否需要生产一个代理对象,看里面的判断逻辑,主要目的是获取一个早产的bean1、里面参数理解:font color=\"#4669ea\
实例化后执行注册的处理器中的方法(如果有的话),如果返回了false,则不进行属性装配,直接返回在设置属性之前,让任何 InstantiationAwareBeanPostProcessors 有机会修改 bean 的状态。例如,这可用于支持字段注入样式,同样可以抑制属性注入
invokeInitMethods()
一级缓存
删除二级缓存this.earlySingletonObjects.remove(beanName);
结束
applyPropertyValues()解析bean定义的里面的属性,如果有引用对象就会产生依赖问题
getBean(beanName)
翻译
解析beanName定义相关的class对象
PUT二级
singletonObject = singletonFactory.getObject();执行第二个参数的方法,通过三级缓存查。内容很多!!!
if (earlySingletonExposure) {如果这个条件为真,就进行单例bean的循环依赖的检查工作,这个条件是上面三个判断得来的,看代码也能知道,1、满足是单例bean2、容器允许循环依赖3、当前bean有效且正在创建
到此,返回的对象要么被代理,或者是原始对象
boolean newSingleton = false;bean创建前的标识
return beanInstance;
为空,证明没有影响
为了防止对象在后面的初始化(init)时重复代理,在创建代理时,earlyProxyReferences缓存会记录已代理的对象。
开始
二级缓存earlySingletonObjects只有一处可以往里面put值,就是上面的参数是true的情况下为什么需要放到二级缓存,主要是怕还有其他的循环依赖,如果还有的话,直接从二级缓存中就能拿到早期的AService对象。
二级缓存
END
再查二级缓存singletonObject = this.earlySingletonObjects.get(beanName);
标志位改成truenewSingleton = true
如果标志位是true,那么就执行下面方法
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));标记状态标记beanName 是已经创建过至少一次的, 它会一直存留在缓存里不会被移除(除非抛出了异常)
返回
applyBeanPostProcessorsBeforeInitialization
return null
添加已注册单例的集合,包含按注册顺序排列的bean名称this.registeredSingletons.add(beanName);
单例bean处理
三级缓存
spring容器的DefaultSingletonBeanRegistry方法存在两个这样的同名方法,但是里面逻辑不一样第二个getSingleton方法说明
从二级缓存里面移除,其实二级缓存大概率是没有的,只是为了保证 三级和二级天敌,不能共存this.earlySingletonObjects.remove(beanName);
删除三级缓存this.singletonFactories.remove(beanName);
处理@Autowired、@Value、@PostConstruct、@PreDestroy、@Resource注解,主要是把mbd的类解析,然后得到这个bean身上的上面列举的相关注解,解析存储到对应处理器里面,但是不执行,后面再执行
执行方法:applyMergedBeanDefinitionPostProcessors接口:MergedBeanDefinitionPostProcessor处理器方法:postProcessMergedBeanDefinition
通过反射获取bd里面的定义的自定义的初始化方法
引申
添加到当前正常创建bean的集合中singletonsCurrentlyInCreation
主要处理@Autowired、@Value 注解的依赖进行设值
initializeBean()初始化方法
invokeAwareMethods()BeanNameAwareBeanClassLoaderAwareBeanFactoryAware
AbstractAutoProxyCreator#getEarlyBeanReference方法,
dependsOn处理
参数说明:1、beanName2、ObjectFactory<?> singletonFactory第二个参数是一个函数式接口,只有在调用这个接口的时候才会执行,传参的时候不会执行方法的调用
PUT一级
0 条评论
下一页