spring bean的创建过程和解决循环依赖的四次getBean()方法

391 阅读7分钟

Bean 的创建过程

bean的创建过程会经历的几个方法

image.png

  • 对于前两个方法getbaen()doGetbaen()来讲,主要是根据单例工厂去获取ioc三级缓存中的的bean对象,如果ioc容器中没有对应的bean对象,执行后面的创建过程。 这个过程是一个bean的工厂方法正在被调用,但该bean尚未完成创建
public Object getBean(String name) throws BeansException {
    return this.doGetBean(name, (Class)null, (Object[])null, false);
}
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = this.transformedBeanName(name);
    Object sharedInstance = this.getSingleton(beanName);// 根据单例获取ioc容器中的对象
    Object beanInstance;
    ......

这里用到了双重检查锁的方式获取单例对象(不同的版本springboot版本这里结构不一样)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {// 第一重检查
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized(this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) { // 第二重检查
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}
  • createBean()doCreateBean() ioc容器中没有对应的bean对象时,就会去创建bean对象,正在被创建但尚未完成初始化
  • createBeanlnstacne()populateBean() 第一个方法是基于反射的原理去创建bean对象,第二个是对该对象进行赋值处理。 bean完全初始化
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Creating instance of bean '" + beanName + "'");
    }
    ...

    beanInstance = this.doCreateBean(beanName, mbdToUse, args);

    ...
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    ...
    if (instanceWrapper == null) {
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }
    ...
    try {
        this.populateBean(beanName, mbd, instanceWrapper);
        exposedObject = this.initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable var18) {

    ...

}

如何找到getbean方法

找到AbstractApplicationContext类,然后找到refresh()方法

public void refresh() throws BeansException, IllegalStateException {}

在此方法中有一个初始化bean工厂的方法this.finishBeanFactoryInitialization(beanFactory); 这个方法中有一个 beanFactory.preInstantiateSingletons();这样就可以找到getbean() 方法了

spring解决循环依赖

问题复现

定义两个类 A B 对象

public class A {
    private B b;
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
}
public class B {
    private A a;
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
}

我这里采用xml配置类的方式来注入bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="a" class="com.whj.pojo.A">
        <property name="b" ref="b"></property>
    </bean>

    <bean id="b" class="com.whj.pojo.B">
        <property name="a" ref="a"></property>
    </bean>
</beans>

测试方法

public class testBean {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        A a = context.getBean("a", A.class);
        B b = context.getBean("b", B.class);
        System.out.println(a);
        System.out.println(b);
    }
}

image.png

可以看到springboot已经解决了循环依赖的问题,那么是如何解决的呢?

为何需要三级缓存?

循环依赖指的是多个bean之间存在相互依赖的情况,形成了一个循环。在这种情况下,如果不采取特殊的机制,很容易导致初始化时的死锁或者无法正确解析依赖关系,比如在上述的例子中,类A将类B作为一个变量,而类B又将类A作为一个变量,简单理解就是A中有B,B中有A,这样就是一个循环依赖的具体展现,当然也可以是多个类之间的依赖。三级缓存的引入就解决了循环依赖问题,确保bean的创建和初始化过程能够正确进行。

三级缓存

所谓的三级缓存就是spring ioc容器,其本质就是一个map结构(singletonObjectsearlySingletonObjectssingletonFactories),只是不同的缓存所存放的内容不一样

首先找到DefaultSingletonBeanRegistry类,类中定义了三个map结构的常量,分别表示不同的缓存结构

private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);//一级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);//三级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);// 二级缓存
  • 对于一级缓存singletonObjects而言string表示添加bean的名称 object表示一个完整的bean 对象,也叫成品对象,当一个bean完全初始化后,它将被添加到这个缓存中。
  • 对于二级缓存earlySingletonObjects而言string表示添加bean的名称 object表示一个还未赋值的bean 对象,也叫半成品对象,当一个bean正在被创建但尚未完成初始化时,它将被添加到这个缓存中。
  • 对于三级缓存singletonFactories而言string表示添加bean的名称 ObjectFactory<?> 是一个函数式接口,其中包含获取对象的逻辑,这通常是一个lambda表达式。该接口的实现是一个工厂方法,用于创建一个bean。当bean的工厂方法正在被调用,但该bean尚未完全初始化时,Spring将该工厂方法放入这个缓存中。

具体流程

第一次调用getbean()方法

首先调用getbean()方法进入doGetbaen()

image.png 然后从ioc缓存中获取A对象

image.png 此时singletonObject为空,并且this.isSingletonCurrentlyInCreation(beanName)也返回false,表示这个对象不是在创建过程中,直接返回null

image.png

然后去创建bean对象 createBean()doCreateBean() ,经过createBeanlnstacne()方法后(反射的方式创建bean),布尔值earlySingletonExposure判断是否符合循环依赖,如果符合就经过addSingletonFactory() 方法,将用于创建单例对象a的工厂方法放入三级缓存中。这只是一个lambda表达式。

boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
    }

    this.addSingletonFactory(beanName, () -> {
        return this.getEarlyBeanReference(beanName, mbd, bean);
    });
}

image.png

image.png 然后进行初始化操作 this.populateBean(beanName, mbd, instanceWrapper);

第二次调用getbean()方法

由于具有循环依赖,调用populateBean()方法对A初始化时(也就是给A对象的属性进行赋值的时候),发现A对象中包含了B对象,需要对B对象初始化操作,故应该去创建B对象,所以需要重新调用getBean()方法去创建B

image.png

此时B对象也是第一次创建,所以和A刚开始的情况一样,通过双重检查锁去获取ioc容器中的对象时,直接返回null。

image.png 返回null

image.png

经历和对象A创建的过程一样, 经过createBean()doCreateBean()createBeanlnstacne()方法后,判断是否符合循环依赖以后,满足条件就将用于创建单例对象a的工厂方法放入三级缓存中。只是一个lambda表达式。(和将A对象放入三级缓存中是一样的步骤)

image.png

第三次调用getbean()方法

由于具有循环依赖,即B对象中包含了A对象,需要对A对象初始化操作,故应该去创建a对象,所以需要重新调用getBean()方法去创建a image.png 此时对于a来说,是正在创建的过程中,所以对于双重检查锁的第一个判断,就能通过。

image.png 由于还未将对象放入二级缓存中,此时在二级缓存获取不到对象

image.png

判断三级缓存是否有A对象 image.png

继续往下通过三级缓存的lambda表达式去获取对象的实例 image.png

放入二级缓存,删除三级缓存,这时候对象a已经创建,但是还未初始化,是一个半成品对象 image.png

由于a是一个半成品对象,通过getSingleton() 方法直接返回给B的工厂方法,完成B的创建(不同的版本这里实现的不一样)

B创建完成以后将二级缓存的数据移除,添加到一级缓存。 image.png

第四次调用getbean()方法

完成对A对象的创建,需要判断b对象是否创建

image.png

获取容器中b对象 image.png

一级缓存存在B对象,返回给A的工厂方法,完成A的创建 image.png

将a放入一级缓存中,删除二级缓存 image.png

三级缓存是在哪解决的?

通过四次getBean方法的调用,可以很明显知道解决三级缓存是在bean的创建过程中,但是哪一个方法最能体现呢?

答案是:AbstractAutowireCapableBeanFactory 类中的doCreateBean()方法

在这个方法中,Spring 容器进行了一系列的处理,包括实例化 bean、依赖注入、初始化方法的调用等。 核心的方法就是


boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
    }

    this.addSingletonFactory(beanName, () -> {
        return this.getEarlyBeanReference(beanName, mbd, bean);
    });
}

检查是否有循环依赖,如果有,就将半成品bean对象实例放入三级缓存中。

image.png

总结

  1. 第一次getBean()调用:

    • 容器检测到需要创建的bean A存在循环依赖。
    • 容器创建A的半初始化实例,并将其提前暴露给其他bean。
    • A的工厂方法可能被调用,引发对B的getBean()调用。
  2. 第二次getBean()调用:

    • 容器检测到需要创建的bean B存在循环依赖。
    • 容器创建B的半初始化实例,并将其提前暴露给其他bean。
    • B的工厂方法可能被调用,引发对A的getBean()调用。
  3. 第三次getBean()调用:

    • 容器检测到A的半初始化实例存在,直接返回给B的工厂方法,完成B的创建。
  4. 第四次getBean()调用:

    • 容器检测到B的半初始化实例存在,直接返回给A的工厂方法,完成A的创建。