@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<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将拒绝启动存在循环依赖的项目

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值