Spring之循环依赖

什么是循环依赖?

依赖的相互引用,如下列的这种形式

@Component
public class A {

    @Autowired
    private B b;
    
}

@Component
public class B {

    @Autowired
    private A a;
}

Spring是如何解决循环依赖的

Spring是通过三级缓存来解决循环依赖

  • singletonObjects : 单例bean,已经实例化,完成属性填充
  • earlySingletonObjects : 半成品bean,已经实例化,未完成属性填充
  • singletonFactories : 一个函数式接口

为什么要使用三级缓存?

因为Spring的动态代理流程是在属性填充流程之后的,如果只使用二级缓存,半成品bean中不确定是放一个普通对象还是代理对象,所以需要使用三级缓存来保存一个不确定的值

PS : 如果只使用二级缓存,那必须所有对象都进行动态代理,那样会生成很多class文件,浪费内存

图解一般情况下的循环依赖

 PS : 动态代理对象的拦截器中保存着原始对象,原始对象走完bean的生命周期,所以将原始对象替换成动态代理对象是可行的。需要注意的是如果bean实现EnvironmentAware这样的方法,期望给bean的属性environment赋值,因为代理对象没经过这样的生命周期,所以代理对象的environment的属性值为null,只有原始对象的environment属性才被赋值了

Spring真的能完全解决循环依赖? 

改造一下上文代码

@Component
public class A {

    private B b;

    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {

    @Autowired
    private A a;
}

很遗憾,Spring没能解决这种构造器与setter注入形式的循环依赖,我们来分析具体原因

前置知识点

  1. 如果一个类有且仅有一个有参构造方法,Spring会以此构造方法实例化对象
  2. 实例化对象之前,会先处理参数的依赖。比如上述代码中bean A的创建过程中,会先getBean(b),再实例化对象A

源码分析

AbstractBeanFactory#doGetBean

我们先看一下这个getSingleton方法本身,而不是createBean方法

由上述源码我们可以得知:执行createBean方法之前会将beanName加入到singletonsCurrentlyInCreation列表中,表明这个bean正在创建

后续b进行属性填充的时候会先从singletonFactories中获取a,因为a还没有进行实例化,没有放入singletonFactories中,所以又会执行getSingleton方法,因为singletonsCurrentlyInCreation这个列表已经存在a这个beanName,所以if条件成立,抛出异常

图解流程

构造方法与setter注入的循环依赖,如果构造方法对应的bean先处理,则会抛出异常,后处理则不会抛出异常。

注意 : bean A 还没有实例化,singletonFactories​​中还没有提前暴露的对象

如果将上述代码改下列形式,则不会抛出异常

@Component
public class A {
    
    @Autowired
    private B b;
}

@Component
public class B {

    private A a;

    public B(A a) {
        this.a = a;
    }
}

PS :针对A先实例化,B后实例化 

综上所述

  • setter和setter的循环依赖:正确注入
  • 构造方法和setter的循环依赖:抛出异常
  • setter和构造方法的循环依赖:正确注入
  • 构造方法和构造方法的循环依赖:抛出异常

普通情况下,我们标记bean正在创建,后续会将实例对象提前暴露放入singletonFactories中,但是构造方法和setter方式的循环依赖在标记bean正在创建后,并没有立刻将实例对象放入singletonFactories中,而是处理构造方法中参数所对应的依赖,如果这个参数对应的依赖,循环依赖了当前bean,则会抛出异常。即标记正在创建的bean,在对象提前暴露放入singletonFactories之前,不能注入一个依赖当前bean的bean

Spring如何解决循环依赖?

SpringBoot2.6及以上版本默认不支持循环依赖,那如何解决循环依赖呢?

主要原因就是SpringBoot将allowCircularReferences默认值设置为false

从配置角度

1.在application.properties文件中将该值设置为true

spring.main.allow-circular-references=true

2.使用bfpp将该值设置为true

@Component
public class FirstBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) registry;
        defaultListableBeanFactory.setAllowCircularReferences(true);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}
从代码角度

1.使用@Lazy注解

@Component
public class A {

    @Autowired
    @Lazy
    private B b;
    
}

@Component
public class B {

    @Autowired
    private A a;

}

2.使用@Lookup注解

@Component
public class A {

    private B b;

    @Lookup
    public B getB() {
       return this.b;
    }
}

@Component
public class B {

    @Autowired
    private A a;

}

3.实现ApplicationContextAware接口

@Component
public class A implements ApplicationContextAware {

    private B b;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.b = applicationContext.getBean(B.class);
    }

}

@Component
public class B {

    @Autowired
    private A a;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值