spring解决循环依赖问题

Posted 唐僧洗澡不秃头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring解决循环依赖问题相关的知识,希望对你有一定的参考价值。

一、简介:

循环依赖就是循环引用,就是两个或多个bean互相之间持有对方。比如beanA引用beanB,beanB引用beanA,当我们实例化beanA的时候发现beanB作为beanA的成员对象出现了,那么我们就可能在实例化beanA的中间需要先实例化beanB,然后完成beanB的实例化之后,才能完成beanA的实例化;可惜的是beanB中也引用了beanA,在实例化beanB过程中又需要实例化beanA,而beanA正在进行实例化,但完成beanA的实例化的条件是beanB实例化完成,完成beanB实例化的条件是完成beanA的实例化,于是他们最终反映为一个环状依赖,难以完成实例化。

二、spring中循环依赖的三种情况

1、构造器注入形成的循环依赖

beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。

2、setter注入构成的循环依赖

beanA需要在beanB的setter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖,也是今天要讨论的重点。

3、prototype作用域bean的循环依赖

这种循环依赖同样无法解决,因为spring不会缓存prototype作用域的bean,而spring中循环依赖的解决方式正是通过缓存来实现的。

三、spring对应循环依赖的解决

spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field是可以延后设置的。spring单例对象的初始化其实可以分为三步:实例化、填充属性、初始化。

实例化(createBeanInstance):就是调用对应的构造方法构造对象,此时只是调用了构造方法,并没有进行属性填充。

填充属性(populateBean):填充属性。

初始化(initializeBean):调用spring xml中指定的init方法,或者AfterPropertiesSet方法。

四、spring中的三级缓存

对于单例对象来说,在spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了”三级缓存“。

// 一级缓存
private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);
 
// 三级缓存
private final Map<String,ObjectFactory<?>> singletonFactories = new HashMap<>();
 
// 二级缓存
private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);

一级缓存:singletonObjects,存放完成实例化,且属性赋值完成的单例对象的cache,直接可以使用。

二级缓存:earlySingletonObjects,存放提前曝光的单例对象的cache,尚未进行属性封装的Bean。

三级缓存:singletonFactories,存放单例对象工厂的cache。

五、spring如何解决循环依赖

spring在创建bean的工程中,首先会尝试从缓存中获取,这个缓存指的就是上面的singletonObjects(一级缓存),主要调用的方法如下:

   protected Object getSingleton(String beanName, boolean allowEarlyReference) {
       Object singletonObject = this.singletonObjects.get(beanName);
       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
          synchronized (this.singletonObjects) {
             singletonObject = this.earlySingletonObjects.get(beanName);
             if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                   singletonObject = singletonFactory.getObject();
                   this.earlySingletonObjects.put(beanName, singletonObject);
                   this.singletonFactories.remove(beanName);
                }
             }
          }
       }
       return (singletonObject != NULL_OBJECT ? singletonObject : null);
   }

isSingletonCurrentlyInCreation():判断对应的单例对象是否在创建中,(例如BeanA在填充属性的过程中依赖了BeanB对象,得先去创建BeanB对象,此时BeanA处于创建中)。

allowEarlyReference():是否允许从singletonFactories(三级缓存)中通过getObject拿到对象。

分析下getSingleton()的整个过程:

步骤一:

spring首先从singletonObjects(一级缓存)中尝试获取。

步骤二:

如果在一级缓存中获取不到并且对象处于创建中状态,则尝试从earlySingletonObjects(二级缓存)中获取。

步骤三:

如果还是获取不到并且允许从singletonFactories(三级缓存)通过getObject()获取,则通过singletonFactory.getObject()(三级缓存)获取。如果获取到了则移除对应的singletonFactory,将singletonObject放入到earlySingletonObjects(二级缓存),其实就是将三级缓存提升到二级缓存中!

  this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);

spring解决循环依赖的诀窍就在于singletonFactories(三级缓存)这个cache,这个cache中存的是类型为ObjectFactory,其定义如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

在bean创建过程中,有两处比较重要的匿名内部类实现了该接口,上面已经提到了,spring利用他来创建bean

new ObjectFactory<Object>() {
    @Override   
    public Object getObject() throws BeansException {
        try {
            return createBean(beanName, mbd, args);
        }catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }   
    }
}

另一处就是:

 addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override  
        public Object getObject() throws BeansException {
             return getEarlyBeanReference(beanName, mbd, bean);
        }
    });

此处就是解决循环依赖的关键,这段代码发生在createBeanInstance(实例化)之后,也就是说单例对象此时已经被创建出来的。这个对象已经被生产出来了,虽然还不完美(还没有进行填充属性和初始化),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

六、总结:

BeanA的某个field或者setter依赖了BeanB的实例对象,同时BeanB的某个field或者setter依赖了BeanA的实例对象,出现这种循环依赖的情况。

BeanA首先完成了初始化的第一步(实例化),并且将自己放到singletonFactories(三级缓存)中,此时进行初始化的第二步(属性填充),发现自己依赖对象BeanB,此时就尝试去get(BeanB),发现BeanB还没有被create,所以走create流程。

BeanB首先完成初始化第一步(实例化),然后在属性填充的时候发现自己依赖了对象BeanA,于是尝试get(BeanA),尝试从一级缓存singletonObjects(肯定没有,因为BeanA还没初始化完全),尝试从二级缓存earlySingletonObjects(也没有),尝试从三级缓存singletonFactories中获取,由于BeanA通过ObjectFactory将自己提前曝光了,所以BeanB能够通过ObjectFactory.getObject()拿到BeanA对象(虽然BeanA还没有初始化完全,但是可以被发现),

BeanB拿到BeanA对象后顺利完成了初始化阶段1、2、3(实例化、填充属性、初始化),完全初始化之后将自己放入到一级缓存singletonObjects中。并且将BeanA放到二级缓存中,移除三级缓存中的BeanA。

此时返回BeanA中,BeanA此时能拿到BeanB的对象顺利完成自己的初始化阶段2、3(填充属性和初始化),最终BeanA也完成了初始化,将BeanA也添加到了一级缓存singletonObjects中。

知道了这个原理时候,肯定就知道为啥Spring不能解决“BeanA的构造方法中依赖了BeanB的实例对象,同时BeanB的构造方法中依赖了BeanA的实例对象”这类问题了!因为BeanB连初始化的第一步(实例化)都完成不了,无法进行下一步的操作。

以上是关于spring解决循环依赖问题的主要内容,如果未能解决你的问题,请参考以下文章

Java面试小短文Spring 如何解决循环依赖?

Java面试小短文Spring 如何解决循环依赖?

spring的循环依赖是什么,设计的时候怎么避免,而spring又是怎么解决的?

spring的循环依赖是什么,设计的时候怎么避免,而spring又是怎么解决的?

Spring循环依赖原因及如何解决

深谈Spring如何解决Bean的循环依赖