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

Bean 的创建过程

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

  • 对于前两个方法getbaen()doGetbaen()来讲,主要是根据单例工厂去获取ioc三级缓存中的的bean对象,如果ioc容器中没有对应的bean对象,执行后面的创建过程。 这个过程是一个bean的工厂方法正在被调用,但该bean尚未完全初始化
java 复制代码
public Object getBean(String name) throws BeansException {
    return this.doGetBean(name, (Class)null, (Object[])null, false);
}
java 复制代码
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版本这里结构不一样)

java 复制代码
@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完全初始化
java 复制代码
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);

    ...
java 复制代码
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()方法

java 复制代码
public void refresh() throws BeansException, IllegalStateException {}

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

spring解决循环依赖

问题复现

定义两个类 A B 对象

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

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

xml 复制代码
<?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>

测试方法

java 复制代码
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);
    }
}

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

三级缓存

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

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

java 复制代码
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()

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

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

然后去创建bean对象 createBean()doCreateBean() ,经过createBeanlnstacne()方法后,将保存的是用于创建单例对象a的工厂方法放入三级缓存中。只是一个lambda表达式。

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

第二次调用getbean()方法

由于具有循环依赖,即A对象中包含了B对象,需要对B对象初始化操作,故应该去创建B对象,所以需要重新调用getBean()方法去创建B

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

返回null

经历和对象A创建的过程一样, 经过createBean()doCreateBean()createBeanlnstacne()方法后,将保存的是用于创建单例对象a的工厂方法放入三级缓存中。只是一个lambda表达式。

第三次调用getbean()方法

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

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

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

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

第四次调用getbean()方法

创建b的半成品对象 经历相同的操作以后 将b放入一级缓存中,删除二级缓存

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

总结

  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的创建。
相关推荐
麻芝汤圆7 分钟前
MapReduce 的广泛应用:从数据处理到智能决策
java·开发语言·前端·hadoop·后端·servlet·mapreduce
努力的搬砖人.7 分钟前
java如何实现一个秒杀系统(原理)
java·经验分享·后端·面试
哈哈哈哈哈哈哈哈哈...........14 分钟前
【java】在 Java 中,获取一个类的`Class`对象有多种方式
java·开发语言·python
fallwind_of_july19 分钟前
java项目分享-分布式电商项目附软件链接
java·redis·分布式·mongodb·elasticsearch·微服务·rabbitmq
怒放吧德德22 分钟前
实际应用:使用Nginx实现代理与服务治理
后端·nginx
6<725 分钟前
【go】空接口
开发语言·后端·golang
武昌库里写JAVA35 分钟前
Golang的消息中间件选型
java·开发语言·spring boot·学习·课程设计
Asthenia041237 分钟前
BCrypt vs MD5:加盐在登录流程和数据库泄露中的作用
后端
工一木子38 分钟前
大厂算法面试 7 天冲刺:第6天-树与图深度剖析——高频算法面试题 & Java 实战
java·算法·面试
追逐时光者1 小时前
由 MCP 官方推出的 C# SDK,使 .NET 应用程序、服务和库能够快速实现与 MCP 客户端和服务器交互!
后端·.net·mcp