浅析Spring三级缓存解决循环依赖

今天主要分享的是Spring如何解决循环依赖的?这个也是一个Spring的高频面试题。下面我们就从源码的角度去剖析下这个问题。

一、简介

二、Spring的Bean创建过程

三、Spring如何解决循环依赖

四、小结

来吧!

一、简介

在我们实际开发过程中,如果我们new出来的对象循环引用,那么就会死循环,直到OOM,然后挂掉。假如在线上发生,那么后果不堪设想。

我们都知道,我们自己new出来的对象压根就不在IOC容器中,IOC都不带我们玩。在IOC容器中的Bean都是经过IOC容器的加载进去的。具体可以移步

源码专题——Spring IOC 容器初始化解析(上)[1]

源码专题——Spring IOC 容器初始化解析(中)[1]

源码专题——Spring IOC 容器初始化解析(下)[1]

二、Spring的Bean创建过程

这里我们还是回顾一下,Spring的Bean的创建,首先创建早期对象(还没有实例化的对象),然后添加到早期对象

首先创建Bean实例的包装类,方便后面使用

/**
 * 真正的创建bean实例(这里创建实例包装类),调用构造方法或者工厂方法
 */
instanceWrapper = createBeanInstance(beanName, mbd, args);

添加到我们的缓存中,这里就是我们的单利缓存池,解决循环依赖的关键所在

/**
 * 添加到我们的单例工厂中缓存起来
 *
 * 提早暴露早期对象 (还没有进行初始化的对象)  就是还没有进行赋值的对象就是早期对象
 *
 */
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

然后就是Bean的赋值,初始化Bean

/**
 * 给我们的Bean进行赋值
 */
populateBean(beanName, mbd, instanceWrapper);
/**
 * 初始化bean
 */
exposedObject = initializeBean(beanName, exposedObject, mbd);

下面我们来具体的讲讲。

三、Spring如何解决循环依赖

Spring也不是说所有的循环依赖都能解决的,我们先抛出结果,然后透过现象看本质。一步步来分析为什么构造器和多例解决不了循环依赖,而只有set才能解决,又是什么原理呢?

我们知道,Spring的Bean创建先是对象的创建,然后是对象的实例化。并不是什么时候都是一来就创建对象,首先去缓存里面捞一波,如果捞到了就直接返回了的。前面讲过,实例化对象的过程很浪费性能的。

一级缓存,singletonObjects存的是完成创建的对象,也就是实例化了的对象。 二级缓存是earlySingletonObjects,存储早期对象三级缓存是singletonFactory,在creatBeanInstance时候放入

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

    /**
     * singletonObjects 这个就是我们大名鼎鼎的单例缓存池
     */
    Object singletonObject = this.singletonObjects.get(beanName);

    /**
     * singletonObject == null 并且单例正在创建
     *
     * isSingletonCurrentlyInCreation就是个标识,标识这个bean是否正在被创建。
     */
    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;
}

如果没捞到,那继续去父工厂中捞一波,如果拿到就返回。

/**
 * 如果缓存中没有拿到,就去检查父工厂里面有没有
 */
BeanFactory parentBeanFactory = getParentBeanFactory();

如果还是没有,那只能自己创建对象了

/**
  * 真正的创建bean实例了,调用构造方法或者工厂方法
  */
instanceWrapper = createBeanInstance(beanName, mbd, args);

把早期对象添加到我们的单例缓存池中

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");

    /**
     * singletonObjects 单例缓存池
     * singletonFactories  单例工厂
     * earlySingletonObjects 早期单例对象
     */
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

好,到这里我们看个例子,假如我有两个对象 InstanceAInstanceB

@Component
public class InstanceA {

    private InstanceB instanceB;

    public InstanceB getInstanceB() {
        return instanceB;
    }

    public void setInstanceB(InstanceB instanceB) {
        this.instanceB = instanceB;
    }
}
@Component
public class InstanceB {

    private InstanceA instanceA;

    public InstanceA getInstanceA() {
        return instanceA;
    }

    public void setInstanceA(InstanceA instanceA) {
        this.instanceA = instanceA;
    }
}

从容器中获取实例对象A

AnnotationConfigApplicationContext context = 
       new AnnotationConfigApplicationContext(BeanConfig.class);

   context.getBean("instanceA");

我直接去getBean获取A实例,那么将会先去getBean(A class),然后缓存池获取不到,去创建A对象,然后把早期对象加入到单例缓存池。这个时候发现A依赖了B对象,先去创建B对象,然后把B加入到单例缓存池;发现B又依赖A对象,去创建A,这个时候A已经在缓存池中了。直接返回早期对象的A。

image

从图中我们可以很清楚的看到,B对象的A属性是在第三步中注入的半成品A对象,而A对象的B属性是在第二步中注入的成品B对象,此时半成品的A对象也就变成了成品的A对象,因为其属性已经设置完成了。以上就是set循环依赖的解决方案。 那么为什么多例解决不了循环依赖呢? 因为多例根本就没有缓存,更别谈早期对象了,我们的单例缓存池不是白叫的。 那么构造器呢? 我们再看看源码,创建bean实例的时候是在这里,我们继续进入里面

/**
 * 真正的创建bean实例了,调用构造方法或者工厂方法
 */
instanceWrapper = createBeanInstance(beanName, mbd, args);

在这一步就进行构造注入,然而我们的早期对象暴露是在下一步,这里还没有暴露,所以解决不了循环依赖

// Candidate constructors for autowiring?
  Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
          mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        //通过构造器进行诸注入
    return autowireConstructor(beanName, mbd, ctors, args);
}

四、小结

对于整体过程,只要理解两点:

  • Spring是通过递归的方式获取目标Bean及其所依赖的Bean的;

  • Spring实例化一个Bean的时候,是分两步进行的,首先实例化目标Bean,然后为其注入属性。

结合这两点,也就是说:Spring在实例化一个Bean的时候,是首先递归的实例化其所依赖的所有Bean,直到某个Bean没有依赖其他Bean,此时就会将该实例返回,然后反递归的将获取到的Bean设置为各个上层Bean的属性的。

服务推荐

猜你喜欢

转载自blog.csdn.net/weixin_48726650/article/details/107717070