@Validated+循环依赖报错分析解决

目录

测试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注解就解决不了了?

测试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,保证单例
  • 第二级缓存:earlySingletonObjects,用于保存实例化完成的bean实例(半成品), Map为了避免因为 AOP 创建多个对象,存储的是半成品的 AOP 的单例 bean
  • 第三级缓存:singletonFactories,用于保存bean创建工厂,以便后续创建代理对象(工厂)Map>,代理工厂,生成半成品的代理对象/原对象

 核心逻辑

@Validated+循环依赖报错分析解决_第1张图片

  • 先从一级缓存去获取,存在就返回,
  • 不存在就去二级缓存中去获取, 存在就返回,
  • 不存在就去找三级缓存,存在就将bean放入二级缓存中并清空三级缓存

循环依赖bean实例化初始化过程

@Validated+循环依赖报错分析解决_第2张图片

源码解读

注:测试demo将myZmTest1的@Validated去掉,去走源码

代码入口:org.springframework.context.support.AbstractApplicationContext#refresh

refresh -> finishBeanFactoryInitialization -> preInstantiateSingletons(实例化单例对象)

第一层,生成myZmTest1

调用getBean方法,定位到myZmTest1

@Validated+循环依赖报错分析解决_第3张图片

 进入getBean方法,调用doGetBean

@Validated+循环依赖报错分析解决_第4张图片

@Validated+循环依赖报错分析解决_第5张图片

进入 doCreateBean方法后,干两件事,先调用addSingletonFactory,将myZmTest1工厂放入第三级缓存

 @Validated+循环依赖报错分析解决_第6张图片

@Validated+循环依赖报错分析解决_第7张图片

第二件事,调用populateBean,注入myZmTest2对象

进入到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties@Validated+循环依赖报错分析解决_第8张图片

获取到依赖属性myZmTest2 

@Validated+循环依赖报错分析解决_第9张图片

@Validated+循环依赖报错分析解决_第10张图片

@Validated+循环依赖报错分析解决_第11张图片

第二层,myZmTest1依赖myZmTest2,去生成myZmTest2

最终是根据org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)获取myZmTest2的bean

实例化myZmTest2前面与1一致,直到2依赖1需要去注入1

@Validated+循环依赖报错分析解决_第12张图片

第三层,myZmTest2依赖myZmTest1,获取myZmTest1去注入

获取myZmTest1的bean@Validated+循环依赖报错分析解决_第13张图片

@Validated+循环依赖报错分析解决_第14张图片

 入方法情况,解循环依赖核心,目前在第三级缓存中已存在myZmTest1与myZmTest2对象@Validated+循环依赖报错分析解决_第15张图片

@Validated+循环依赖报错分析解决_第16张图片

回到第二层,获取到myZmTest1,去注入myZmTest2

回到myZmTest2注入myZmTest1处@Validated+循环依赖报错分析解决_第17张图片

 获取到myZmTest1的bean对象,注入到myZmTest2中,依赖完成,此时依赖的是第三级缓存中的myZmTest1@Validated+循环依赖报错分析解决_第18张图片

 将myZmTest2实例化完成后,获取将实例化完成的且完整的myZmTest2放入到一级缓存中@Validated+循环依赖报错分析解决_第19张图片

@Validated+循环依赖报错分析解决_第20张图片

回到第一层,获取到myZmTest2,去注入myZmTest1

注入完myZmTest2后,将以初始化完成的myZmTest1对象放到第一层缓存中,结束

最终在第一层缓存中,存在myZmTest1与myZmTest2,完成bean初始化

@Validated+循环依赖报错分析解决_第21张图片

总结代码主要调用流

@Validated+循环依赖报错分析解决_第22张图片

循环依赖+@Validated注解报错解析

报错位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

@Validated+循环依赖报错分析解决_第23张图片

可以看出,执行完initializeBean后生成的bean与三级缓存中的bean不一致,所以报错

位置在myZmTest1注入myZmTest2后,重点看注入完属性后的初始化方法initializeBean

@Validated+循环依赖报错分析解决_第24张图片

初始化完成后的后处理器 

@Validated+循环依赖报错分析解决_第25张图片

 定位到@Validation注解对应的处理器

@Validated+循环依赖报错分析解决_第26张图片

进入org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization@Validated+循环依赖报错分析解决_第27张图片

 isEligible:是否生成代理类

@Validated+循环依赖报错分析解决_第28张图片

@Validated+循环依赖报错分析解决_第29张图片

最终判断为true

@Validated+循环依赖报错分析解决_第30张图片

总结来看

@Validated+循环依赖报错分析解决_第31张图片

解决方法

在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的比较。

@Validated+循环依赖报错分析解决_第32张图片

总结来说,循环依赖本身就是一种不好的现象,所以在开发过程中,如果出现了循环依赖,就需要通过提取共性或者修改代码结构去避免这种情况

另外,从2.6版本开始,SpringBoot将拒绝启动存在循环依赖的项目

你可能感兴趣的:(Java,循环依赖,三级缓存源码,Validation注解)