【Spring源码:循环依赖】一文弄懂Spring循环依赖

1. 什么是循坏依赖

很简单,其实就是互相依赖对方,比如,有一个A对象依赖了B对象,B对象又依赖了A对象。


// A依赖了B
public class A{
    private B b;
}
 
// B依赖了A
public class B{
    private A a;
}

但是,在我们普通的java开发中,循坏依赖会出现问题吗?

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖注入是很正常的事情。

比如:

这样,A,B就互相依赖上了。

但是,在Spring中循环依赖就是一个问题了,为什么?

因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Spring Bean的生命周期,正是因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员来解决,下面具体分析。

要明白Spring中的循环依赖,得先弄明白Spring中Bean的生命周期。

2. 前置知识

2.1 Spring bean 生命周期

这里不会对Bean的生命周期进行详细的分析,只描述一下大概的过程。

Bean的生命周期指的就是:在Spring启动中,Bean是如何生成的?

被Spring管理的对象叫做Bean。Bean的生成步骤如下:

class --> BeanDefinition --> 实例化(前后)--> 属性填充 --> 初始化(前后) --> bean 加入单例池

  1. Spring扫描class得到每个beanName对应的BeanDefinition;
  2. 根据得到的BeanDefinition去生成bean;
  3. 首先根据class推断构造方法;
  4. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象/普通对象);
  5. 填充原始对象中的属性(依赖注入);
  6. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象;
  7. 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可。

可以发现,在Spring中,构造一个Bean,包括了new这个步骤(第4步构造方法反射-->实例化)。

通过反射构造了一个原始对象之后,就需要进行属性填充(依赖注入),注入的过程大致如下:

上面的A类需要注入一个B类的b属性,则当A实例化之后,就需要去给b属性赋值。在属性填充之前,会先去获得属性值,但是此时B对象还没有被实例化,所以需要先去实例化B才可以。此时递归的去构建B对象。同理,在创建B类的Bean的过程中,B类中也存在一个A类的a属性,在B实例化之后,进行属性填充,又去获取A对象(但是A正在创建中),从而形成循环依赖:

ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中)

从而导致ABean创建不出来,BBean也创建不出来。

因此,下面我们具体分析,在Spring中,它帮我们解决了哪些场景的循环依赖。

2.2 主要用到4个集合缓存

  • Map<String, Object> singletonObjects (一级缓存)

    key:beanName,value:经历了spring完整生命周期的bean对象

  • Map<String, Object> earlySingletonObjects(二级缓存)

    key:beanName,value:不完整的单例对象,也就是还没有初始化完毕

  • Map<String, ObjectFactory<?>> singletonFactories(三级缓存)

    key:beanName,value:单例工厂,用于创建beanName所对应的Bean对象

  • Set< String > singletonsCurrentlyInCreation

    set集合,存放正在创建的Bean的名称,创建完成后,从这个集合中删除。

3. 调试与分析

3.1 调试

@Component
public class A {

    @Autowired
    private B b;
}

@Component
public class B {

    @Autowired
    private A a;
}

@Configuration
@ComponentScan("com.example.demo.test")
public class AppConfig {
}

public class test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(AppConfig.class);
    }
}

熟悉Spring Bean生命周期源码的读者,应该都知道,我们可以直接跟进在AbstractApplicationContext#refresh()中的代码:

// 完成所有剩余的非懒加载的单例bean的实例化与初始化过程
finishBeanFactoryInitialization(beanFactory);

继续跟进到 beanFactory.preInstantiateSingletons()

如上,遍历所有的beanNames,判断当前创建的bean

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值