目录
测试demo
回顾下三级缓存
循环依赖bean实例化初始化过程
源码解读
第一层,生成myZmTest1
第二层,myZmTest1依赖myZmTest2,去生成myZmTest2
第三层,myZmTest2依赖myZmTest1,获取myZmTest1去注入
回到第二层,获取到myZmTest1,去注入myZmTest2
回到第一层,获取到myZmTest2,去注入myZmTest1
总结代码主要调用流
循环依赖+@Validated注解报错解析
解决方法
报错信息
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注解就解决不了了?
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;第三步完成属性注入
核心逻辑
注:测试demo将myZmTest1的@Validated去掉,去走源码
代码入口:org.springframework.context.support.AbstractApplicationContext#refresh
refresh -> finishBeanFactoryInitialization -> preInstantiateSingletons(实例化单例对象)
调用getBean方法,定位到myZmTest1
进入getBean方法,调用doGetBean
进入 doCreateBean方法后,干两件事,先调用addSingletonFactory,将myZmTest1工厂放入第三级缓存
第二件事,调用populateBean,注入myZmTest2对象
进入到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
获取到依赖属性myZmTest2
最终是根据org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)获取myZmTest2的bean
实例化myZmTest2前面与1一致,直到2依赖1需要去注入1
入方法情况,解循环依赖核心,目前在第三级缓存中已存在myZmTest1与myZmTest2对象
获取到myZmTest1的bean对象,注入到myZmTest2中,依赖完成,此时依赖的是第三级缓存中的myZmTest1
将myZmTest2实例化完成后,获取将实例化完成的且完整的myZmTest2放入到一级缓存中
注入完myZmTest2后,将以初始化完成的myZmTest1对象放到第一层缓存中,结束
最终在第一层缓存中,存在myZmTest1与myZmTest2,完成bean初始化
报错位置: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将拒绝启动存在循环依赖的项目