Spring循环依赖

  循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错,Spring是如果解决循环依赖的

第一种:构造器参数循环依赖

  Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。

  因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

public class StudentA {
 
    private StudentB studentB ;
 
    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
 
    public StudentA() {
    }
 
    public StudentA(StudentB studentB) {
        this.studentB = studentB;
    }
 
}
 
 
public class StudentB {
 
    private StudentC studentC ;
 
    public void setStudentC(StudentC studentC) {
        this.studentC = studentC;
    }
 
    public StudentB() {
 
    }
 
    public StudentB(StudentC studentC) {
        this.studentC = studentC;
    }
 
}
 
 
public class StudentC {
 
    private StudentA studentA ;
 
    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
 
    public StudentC() {
 
    }
 
    public StudentC(StudentA studentA) {
        this.studentA = studentA;
    }
 
}

  StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,把这三个Bean交给Spring管理,并用有参构造实例化。

<bean id="a" class="com.zfx.student.StudentA">
     <constructor-arg index="0" ref="b"></constructor-arg>
 </bean>
 <bean id="b" class="com.zfx.student.StudentB">
     <constructor-arg index="0" ref="c"></constructor-arg>
 </bean>
 <bean id="c" class="com.zfx.student.StudentC">
     <constructor-arg index="0" ref="a"></constructor-arg>
 </bean> 

   测试类

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        //System.out.println(context.getBean("a", StudentA.class));
    }
}

  执行结果报错信息为:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

  Spring容器先创建单例StudentA,StudentA依赖StudentB,然后将A放在“当前创建Bean池”中,此时创建StudentB,StudentB依赖StudentC ,然后将B放在“当前创建Bean池”中,此时创建StudentC,StudentC又依赖StudentA, 但是,此时StudentA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会产生依赖错误 。

 基于setter属性的循环依赖

  Spring先是用构造实例化Bean对象 ,创建成功后,Spring会通过代码提前将对象暴露出来,此时的对象A还没有完成属性注入,属于早期对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象引用的方法。 结合实例来看,当Spring实例化A、B、C后,紧接着会去设置对象的属性,此时A依赖B,就会去Map中取出存在里面的单例B对象,以此类推,不会出来循环的问题

Spring 解决循环依赖的思路

  Spring创建对象包括对象的实例化和属性注入两个阶段,只有完成了实例化和属性注入才算是真正创建完成了,实例化的工作主要是通过对象构造方法实例化对象的过程,属性注入就是把对象锁依赖的属性进行赋值的过程。

Spring的三级缓存

  一级缓存:保存所有已经创建完成的bean。

  二级缓存:保存正常创建中的bean (完成实例化,但还未完成属性注入)。

  三级缓存:保存的是ObjectFactory类型的对象工厂,通过工厂的方法可以获取到目标对象。

  标记缓存:保存着正在创建中的对象名称。

  源码DefaultSingletonBeanRegistry 类

    //一级缓存,保存所有已经创建完成的bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
    //三级缓存,一个创建对象的工厂(其实就是根据这个工厂可以生产或者拿到一个对象)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
​
    //二级缓存,保存的是未完成创建的bean
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

  对象A和对象B 属性注入方式产生循环依赖,程序如下:

@Component
public class ObjectA {
​
    @Autowired
    private  ObjectB b;
​
}
@Component
public class ObjectB {
​
    @Autowired
    private  ObjectA a;
}

  Spring初始化,三级缓存和标记缓存内容都还是空的

  第一步:尝试从缓存获取A对象

  每次创建对象之前,Spring首先会尝试从缓存获取对象,显然A还没有创建过,所以从缓存获取不到,会执行下面的对象实例化流程,尝试创建A对象。

  第二步:实例化A对象

  会进行A对象的实例化,首先会在标记缓存里标记A正在创建中,然后调用构造方法进行实例化,再把自己放到一个ObjectFactory工厂里再保存到三级缓存。

  第三步:A对象进行属性注入

  A需要依赖于B对象,完成A要完成创建首先需要获得B,所以会尝试从一级缓存获取B对象,但是此时一级缓存没有;然后会看B对象是否正处于创建中,显然现在B也还未开始创建,这个时候容器会先去创建完B对象,等拿到B对象的之后,然后再回来完成A的属性注入。

  第四步:实例化B对象

  尝试创建对象B之前,容器还是会尝试先从缓存里面查找,然而没找到;才真正决定进行B对象的实例化,首先会标记B正在创建中,然后调用构造方法进行实例化,再把自己放到一个ObjectFactory工厂对象里保存到三级缓存里。

  第五步:B对象进行属性注入

  进行属性注入的时候,发现B的属性需要依赖于A对象,到这里sping也还是会尝试从缓存获取,先查看一级缓存有没有A对象,但是此时一级缓存没有A对象;

  然后再看A对象是否正处于创建中(这时知道了A正处于创建中),所以就继续从二级缓存中去获取B(二级缓存也没有),最后去三级缓存里面找(此时A对象存在于三级缓存),从三级缓存里获得一个ObjectFactory,然后调用ObjectFactory.getObject()方法得到A对象。拿到A对象后 这里会把A对象从三级缓存移出,然后把A保存到二级缓存。

  第六步:B对象创建完成

  拿到A对象后,spring把A对象的引用赋值给B对象的属性,然后B就完成了创建,最后会把B对象从三级缓存移出,保存到一级缓存里去,同时也会移出创建中的标记。

  第七步:完成A对象的属性注入

  代码流程会返回到第三步,容器已经拿到了B对象了,所以可以继续完成A对象的属性注入工作了。

  拿到B对象后,然后把B对象引用赋值给A的属性,最后同样也会把A对象从二级缓存移出,保存到一级缓存里去,同时也会移出创建中的标记。

  通过三个缓存和一个标记最终就完成了整个循环依赖对象的创建

二级缓存和三缓存的区别

//如果bean正在创建中并且完成了实例化,则把当前bean的获取方式保存到singletonFactories三级缓存中
addSingletonFactory(beanName, () -> 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)) {
                DebugUtil.debug5(beanName);
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

  保存到三级缓存的是getEarlyBeanReference()方法的逻辑,那么当调用从三级缓存里获取对象singletonFactory.getObject()的时候其实调用的是getEarlyBeanReference()方法。

 protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

  方法可能会通过exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);获取对象。当这里BeanPostProcessor 类型为AbstractAutowireCapableBeanFactory 时会执行代码:

/**
     * 尝试获取早期代理对象的引用
     * @param bean the raw bean instance
     * @param beanName the name of the bean
     * @return
     * @throws BeansException
     */
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);
        }
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

  执行 AbstractAutowireCapableBeanFactory 对象的getEarlyBeanReference()方法后返回的是一个代理对象。

   

 

 

 

  

  
  
posted on 2020-08-03 08:02  溪水静幽  阅读(244)  评论(0)    收藏  举报