目录
第二层,myZmTest1依赖myZmTest2,去生成myZmTest2
第三层,myZmTest2依赖myZmTest1,获取myZmTest1去注入
回到第二层,获取到myZmTest1,去注入myZmTest2
回到第一层,获取到myZmTest2,去注入myZmTest1
报错信息
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'myZmTest1': Bean with name 'myZmTest1' has been injected
into other beans [myZmTest2] in its raw version as part of a circular reference, but has
eventually been wrapped. This means that said other beans do not use the final version of
the bean. This is often the result of over-eager type matching - consider using
'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
核心意思是:创建名为“myZmTest1”的bean时出错:名为“myZmTest1”的bean已作为循环引用的一部分注入到原始版本的其他bean[myZmTest2]中,但最终已被包装。这意味着所述其他bean不使用bean的最终版本
大家都知道虽然在开发过程中,不建议出现循环依赖,但是Spring也做了兜底,提供了三级缓存用来解决循环依赖,但为什么循环依赖的类加了参数校验@Validated注解就解决不了了?
测试demo
spring-boot 版本号:2.3.8.RELEASE
@Service
@Validated
public class MyZmTest1 {
@Autowired
private MyZmTest2 myZmTest2;
public void test1(@NonNull Integer id){
System.out.println("MyZmTest1 run..." + id);
}
}
@Service
public class MyZmTest2 {
@Autowired
private MyZmTest1 myZmTest1;
public void test2(){
System.out.println("MyZmTest2 run...");
}
}
回顾下三级缓存
生成bean:第一步创建bean创建工厂;第二步使用工厂创建bean;第三步完成属性注入
- 第一级缓存:singletonObjects,用于保存实例化、注入、初始化完成的bean实例(已完成),Map<String, Object>,保证单例
- 第二级缓存:earlySingletonObjects,用于保存实例化完成的bean实例(半成品), Map<String, Object>为了避免因为 AOP 创建多个对象,存储的是半成品的 AOP 的单例 bean
- 第三级缓存:singletonFactories,用于保存bean创建工厂,以便后续创建代理对象(工厂)Map<String, ObjectFactory<?>>,代理工厂,生成半成品的代理对象/原对象
核心逻辑
- 先从一级缓存去获取,存在就返回,
- 不存在就去二级缓存中去获取, 存在就返回,
- 不存在就去找三级缓存,存在就将bean放入二级缓存中并清空三级缓存
循环依赖bean实例化初始化过程
源码解读
注:测试demo将myZmTest1的@Validated去掉,去走源码
代码入口:org.springframework.context.support.AbstractApplicationContext#refresh
refresh -> finishBeanFactoryInitialization -> preInstantiateSingletons(实例化单例对象)
第一层,生成myZmTest1
调用getBean方法,定位到myZmTest1
进入getBean方法,调用doGetBean
进入 doCreateBean方法后,干两件事,先调用addSingletonFactory,将myZmTest1工厂放入第三级缓存
第二件事,调用populateBean,注入myZmTest2对象
进入到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
获取到依赖属性myZmTest2
第二层,myZmTest1依赖myZmTest2,去生成myZmTest2
最终是根据org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)获取myZmTest2的bean
实例化myZmTest2前面与1一致,直到2依赖1需要去注入1
第三层,myZmTest2依赖myZmTest1,获取myZmTest1去注入
获取myZmTest1的bean
入方法情况,解循环依赖核心,目前在第三级缓存中已存在myZmTest1与myZmTest2对象
回到第二层,获取到myZmTest1,去注入myZmTest2
回到myZmTest2注入myZmTest1处
获取到myZmTest1的bean对象,注入到myZmTest2中,依赖完成,此时依赖的是第三级缓存中的myZmTest1
将myZmTest2实例化完成后,获取将实例化完成的且完整的myZmTest2放入到一级缓存中
回到第一层,获取到myZmTest2,去注入myZmTest1
注入完myZmTest2后,将以初始化完成的myZmTest1对象放到第一层缓存中,结束
最终在第一层缓存中,存在myZmTest1与myZmTest2,完成bean初始化
总结代码主要调用流
循环依赖+@Validated注解报错解析
报错位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
可以看出,执行完initializeBean后生成的bean与三级缓存中的bean不一致,所以报错
位置在myZmTest1注入myZmTest2后,重点看注入完属性后的初始化方法initializeBean
初始化完成后的后处理器
定位到@Validation注解对应的处理器
进入org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization
isEligible:是否生成代理类
最终判断为true
总结来看
解决方法
在MyZmTest2 依赖的MyZmTest1 上添加@Lazy
@Service
@Validated
public class MyZmTest1 {
@Autowired
private MyZmTest2 myZmTest2;
public void test1(@NonNull Integer id){
System.out.println("MyZmTest1 run..." + id);
}
}
@Service
public class MyZmTest2 {
@Lazy
@Autowired
private MyZmTest1 myZmTest1;
public void test2(){
System.out.println("MyZmTest2 run...");
}
}
在属性依赖时,在进行属性注入的时候会先判断:isLazy(),只有isLazy()=false时才会调getBean(MyZmTest1)从第三级缓存中获取工厂创建对象并放入二级缓存(无图示第三层)。
既然加了@Lazy不会将MyZmTest1放入二级缓存,那上面的条件二earlySingletonReference != null就不会成立。也就跳过了条件三:exposedObject != bean的比较。
总结来说,循环依赖本身就是一种不好的现象,所以在开发过程中,如果出现了循环依赖,就需要通过提取共性或者修改代码结构去避免这种情况
另外,从2.6版本开始,SpringBoot将拒绝启动存在循环依赖的项目