spring (11)Spring循环依赖

366 阅读4分钟

1.什么是循环依赖

在bean的属性注入过程中,出现了A依赖B,B依赖A的情况

体现在代码中的情况:

@Component
public class A {
    // A中注入了B
 @Autowired
 private B b;
}

@Component
public class B {
    // B中也注入了A
 @Autowired
 private A a;
}

// 自己依赖自己
@Component
public class A {
    // A中注入了A
 @Autowired
 private A a;
}

2.什么情况下循环依赖可以被处理

  • spring的只有单例bean的循环依赖可以被解决,多例bean的依赖,每次都创建一个新的bean,可能会内存溢出,所以spring检测到该情况就直接包异常.

  • 依赖注入的方式不能够全是采用构造器注入的方式,例如:

@Component
public class A {
// @Autowired
// private B b;
 public A(B b) {

 }
}


@Component
public class B {

// @Autowired
// private A a;

 public B(A a){

 }
}

A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是无法被解决,如果你的项目中有两个这样相互依赖的Bean,在启动时就会报出错误

3.Spring是如何解决循环依赖

3.1没有AOP的情况:

spring创建bean的过程:

  • 实例化,(简单的new一个对象)对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法
  • 属性注入,(对new出来的对象进行属性填充)对应方法:AbstractAutowireCapableBeanFactory的populateBean方法
  • 初始化,(执行aware接口,初始化方法)对应方法:AbstractAutowireCapableBeanFactory的initializeBean

3.1.1 getBean()方法

创建对象的过程就是调用getbean()方法,该方法的作用如下:

  • 从缓存中获取已经被创建的bean
  • 如果缓存中没有,创建一个新的bean

3.1.2 getSingleton(beanName)方法

getBean()方法会调佣getSinglteon(beanName)方法,getSingleton(beanName)方法又会调用getSingleton(beanName, true)方法,getSingleton(beanName, true)方法会尝试去三级缓存中获取对象.

三级缓存

1.singletonObjects,一级缓存,存储的是所有创建好了的单例Bean

2.earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象

3.singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

第一次在缓存中获取不到bean,所以会进入到getSingleton(beanName, singletonFactory)方法.

3.1.3 getSingleton(beanName, singletonFactory)方法

该方法用来创建bean,创建完成会放入到一级缓存singletonFactories中

3.1.4 放入三级缓存

当一个bean实例化完成之后,初始化之前,会将该bean包装成为一个工厂,加入到三级缓存singletonFactories中.

// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

当A创建完成实例化之后,就会放入到三级缓存之中,然后开始属性的注入,在属性注入的过程中发现依赖了B,就会去实例化B,B在实例化完成后,进行B的属性注入,发现依赖A,会从三级缓存中获取未初始化的A.

注入到B中的A是通过getEarlyBeanReference方法提前暴露出去的一个对象,还不是一个完整的Bean

image.png

3.1.5 AOP的循环依赖

image.png

使用三级缓存而非二级缓存并不是因为只有三级缓存才能解决循环引用问题,其实二级缓存同样也能很好解决循环引用问题。使用三级而非二级缓存并非出于IOC的考虑,而是出于AOP的考虑,即若使用二级缓存,在AOP情形下,注入到其他bean的,不是最终的代理对象,而是原始对象。

并不是说二级缓存如果存在aop的话就无法将代理对象注入的问题,本质应该说是初始spring是没有解决循环引用问题的,设计原则是 bean 实例化、属性设置、初始化之后 再 生成aop对象,但是为了解决循环依赖但又尽量不打破这个设计原则的情况下,使用了存储了函数式接口的第三级缓存; 如果使用二级缓存的话,可以将aop的代理工作提前到 提前暴露实例的阶段执行; 也就是说所有的bean在创建过程中就先生成代理对象再初始化和其他工作; 但是这样的话,就和spring的aop的设计原则相驳,aop的实现需要与bean的正常生命周期的创建分离; 这样只有使用第三级缓存封装一个函数式接口对象到缓存中, 发生循环依赖时,触发代理类的生成;