Spring源码学习~11、Bean 的加载步骤详解(二)

Bean 的加载步骤详解(二)

一、循环依赖

1、什么是循环依赖

循环依赖就是循环引用,即两个或多个 bean 互相之间持有对方,如下图:

在这里插入图片描述

循环引用不是循环调用,循环调用是方法之间的环调用,循环调用是无法解决的,除非有终结条件,否则出现死循环,最终导致内存溢出。

1)、Spring 如何解决循环依赖

Spring 容器循环依赖包括构造器循环依赖和 setter 循环依赖,那 Spring 容器如何解决循环依赖呢?先看下示例:

package com.luo.spring.guides.helloworld.beanloading.circulardependency;

import lombok.Data;

/**
 * @author : archer
 * @date : Created in 2022/11/7 19:30
 * @description :
 */
@Data
public class TestA {
   

    private TestB testB;
    
    public void test(){
   
        System.out.println("i am testA");
    }
}
package com.luo.spring.guides.helloworld.beanloading.circulardependency;

import lombok.Data;

/**
 * @author : archer
 * @date : Created in 2022/11/7 19:31
 * @description :
 */
@Data
public class TestB {
   

    private TestC testC;
}
package com.luo.spring.guides.helloworld.beanloading.circulardependency;

import lombok.Data;

/**
 * @author : archer
 * @date : Created in 2022/11/7 19:31
 * @description :
 */
@Data
public class TestC {
   

    private TestA testA;
}

在 Spring 中将依赖循环的处理分成了 3 种情况。

1、构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。如在创建 TestA 类时,构造器需要 TestB,创建 TestB 就需要创建 TestC,而创建 TestC 又需要先创建 TestA,这样就形成了一个环,没办法创建。

Spring 容器将每一个正在创建的 bean 标识符放在一个 “当前创建 bean 池” 中,bean 标识符在创建过程中将一直保持在这个池子中,因此如果在创建 bean 过程中发现自己已经在 “当前创建 bean 池” 中时,将抛出 BeanCurrentlyInCreationException 异常,表示循环依赖;而对于创建完毕的 bean 将从 “当前创建 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="testA" class="com.luo.spring.guides.helloworld.beanloading.circulardependency.TestA">
        <constructor-arg index="0" ref="testB"/>
    </bean>
    <bean id="testB" class="com.luo.spring.guides.helloworld.beanloading.circulardependency.TestB">
        <constructor-arg index="0" ref="testC"/>
    </bean>
    <bean id="testC" class="com.luo.spring.guides.helloworld.beanloading.circulardependency.TestC">
        <constructor-arg index="0" ref="testA"/>
    </bean>

</beans>

创建测试用例

package com.luo.spring.guides.helloworld.beanloading.circulardependency;

import com.luo.spring.guides.helloworld.beanloading.factorybean.Car;
import com.luo.spring.guides.helloworld.beanloading.factorybean.CarFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/11/7 19:56
 * @description :
 */
public class Main {
   

    public static void main(String[] args) {
   
        try {
   
            ApplicationContext bf = new ClassPathXmlApplicationContext("beanloading/circulardependency/circulardependency.xml");
            ((TestA)bf.getBean("testA")).test();
        } catch (BeansException e) {
   
            e.printStackTrace();
        }
    }
}

输出结果

在这里插入图片描述

分析

  • Spring 容器创建 “testA” bean,首先去 “当前创建 bean 池” 查找是否当前 bean 正在创建,若没发现,则继续准备其需要的构造参数 “testB”,并将 “testA” 标识符放到 “当前创建 bean 池”。
  • Spring 容器创建 “testB” bean,首先去 “当前创建 bean 池” 查找是否当前 bean 正在创建,若没发现,则继续准备其需要的构造参数 “testC”,并将 “testB” 标识符放到 “当前创建 bean 池”。
  • Spring 容器创建 “testC” bean,首先去 “当前创建 bean 池” 查找是否当前 bean 正在创建,若没发现,则继续准备其需要的构造参数 “testA”,并将 “testC” 标识符放到 “当前创建 bean 池”。
  • 到此为止 Spring 容器要去创建 “testA” bean,发现该 bean 标识符在 “当前创建 bean 池” 中,表示训话你来,抛出 BeanCurrentlyInCreationException 异常。
2、setter 循环依赖

表示通过 setter 注入方式构成的循环依赖。对于 setter 注入造成的依赖,是通过 Spring 容器提前暴露完成构造器注入,但未完成其他步骤(如 setter 注入)的 bean 来完成的。而且只能解决单例作用域的 bean 循环依赖。对于 singleton 作用域 bean,可以通过 setAllowCircularReferences(false) 来禁用循环引用。

它是通过提前暴露一个单例工厂方法,从而使其他 bean 能引用到该 bean,如下代码所示:

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)) {
   
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

具体步骤如下:

  • 1、Spring 容器创建单例 “testA” bean,首先根据无参构造器创建 bean,并暴露一个 ObjectFactory ,用于返回一个提前暴露的一个创建中的 bean,并将 “testA” 标识符放到 “当前创建 bean 池”,然后 setter 注入 “testB”。
  • 2、Spring 容器创建单例 “testB” bean,首先根据无参构造器创建 bean,并暴露一个 ObjectFactory ,用于返回一个提前暴露的一个创建中的 bean,并将 “testB” 标识符放到 “当前创建 bean 池”,然后 setter 注入 “testC”。
  • 2、Spring 容器创建单例 “testC” bean,首先根据无参构造器创建 bean,并暴露一个 ObjectFactory ,用于返回一个提前暴露的一个创建中的 bean,并将 “testC” 标识符放到 “当前创建 bean 池”,然后 setter 注入 “testA”,进行注入 “testA” 时,由于提前暴露了 ObjectFactory 工厂,从而使用它返回一个提前暴露的一个创建中的 bean。
  • 4、最后再依赖注入 “testB” 和 “testA”,完成 setter 注入。

注意SpringBoot 2.6.0 之后,Spring官方已经不建议循环依赖了,出现循环依赖还是最好从编码层面做解耦比较好,在此版本,以上代码在运行时会出现循环依赖,导致出现 java.lang.StackOverflowError 错误。

3、prototype 范围的依赖处理

对于 prototype 作用域的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存 prototype 作用域的 bean,因此无法提前暴露一个创建中的 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="testA" class="com.luo.spring.guides.helloworld.beanloading.circulardependency.TestA">
        <property name="testB" ref="testB"/>
    </bean>
    <bean id="testB" class="com.luo.spring.guides.helloworld.beanloading.circulardependency.TestB">
        <property name="testC" ref="testC"/>
    </bean>
    <bean id="testC" class="com.luo.spring.guides.helloworld.beanloading.circulardependency.TestC">
        <property name="testA" ref="testA"/>
    </bean>

</beans>

测试

package com.luo.spring.guides.helloworld.beanloading.circulardependency;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/11/7 19:56
 * @description :
 */
public class Main {
   

    //setter 注入 prototype 作用域
    public static void main(String[] args) {
   
        try {
   
            ApplicationContext bf = new ClassPathXmlApplicationContext("beanloading/circulardependency/prototype/prototypecirculardependency.xml");
            ((TestA)bf.getBean("testA")).test();
        } catch (BeansException e) {
   
            e.printStackTrace();
        }
    }
}

输出

在这里插入图片描述

二、创建 bean

//给 BeanPostProcessors(后置处理器) 一个机会来返回代理(替代真正的实例)
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
   
    return bean;
}

可以看出程序经历过 resolveBeforeInstantiation 方法后,如果创建了代理或者说重写了 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法,并在方法postProcessBeforeInstantiation 中改变了 bean,则直接返回,否则就进行常规 bean 的创建。代码如下:

Object beanInstance = doCreateBean(beanName, mbdToUse, args);

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
   

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
   
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
   
        //根据指定 bean 使用对应的策略创建新的实例,如:工厂方法,构造函数自动注入,简单初始化
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
   
        mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
   
        if (!mbd.postProcessed) {
   
            //应用 MergedBeanDefinitionPostProcessor
            try {
   
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
   
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    //是否需要提早曝光:单例 & 允许循环依赖 & 当前 bean 正在创建中,来检查循环依赖
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
   
        if (logger.isTraceEnabled()) {
   
            logger.trace("Eagerly caching bean '" + beanName +
                         "' to allow for resolving potential circular references");
        }
        //为避免后期循环依赖,可在 bean 初始化完成前将创建实例的 ObjectFactory 加入工厂
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
   
        //对 bean 进行填充,将各个属性值注入其中,若存在依赖于其他 bean 的属性,就会递归初始依赖 bean
        populateBean(beanName, mbd, instanceWrapper);
        //调用初始化方法,比如 init-method
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
   
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
   
            throw (BeanCreationException) ex;
        }
        else {
   
            throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    if (earlySingletonExposure) {
   
        Object earlySingletonReference = getSingleton(beanName, false);
        //earlySingletonReference 只有在检测到有循环依赖的情况下才会不为空
        if (earlySingletonReference != null) {
   
            //如果 exposedObject 没有在初始化方法中被改变,即没有被增强
            if (exposedObject == bean) {
   
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
   
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                //检测依赖
                for (String dependentBean : dependentBeans) {
   
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
   
                        actualDependentBeans.add(dependentBean);
                    }
                }
                //因为 bean 创建后其依赖的 bean 一定是已创建的,
                //若 actualDependentBeans 不为空,则表示当期 bean 创建后所依赖的 bean 还没有全部创建完,也就是说存在依赖循环。
                if (!actualDependentBeans.isEmpty()) {
   
                    throw new BeanCurrentlyInCreationException(beanName,
                                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                                                               StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                      "] in its raw version as part of a circular reference, but has eventually been " +
                                      "wrapped. This means that said other beans do not use the final version of the " +
                                      "bean. This is often the result of over-eager type matching - consider using " +
                                       "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
   
        //根据 scope 注册 bean
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
   
        throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

//对 bean 再一次依赖引用,主要应用 SmartInstantiationAwareBeanPostProcessor
//Aop 就是在这里将 advice 动态植入 bean 中,若没有则返回原本的 bean,不做任何处理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   
    Object exposedObject = bean;
    if (!mbd.isSynthetic
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值