Spring源码学习~11Bean 的加载步骤详解

Posted 戴泽supp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码学习~11Bean 的加载步骤详解相关的知识,希望对你有一定的参考价值。

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() && hasInstantiationAwareBeanPostProcessors()) 
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) 
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        
    
    return exposedObject;


我们先来梳理下整个函数的概要思路(先忽略掉日志和异常)

  • 1、如果是单例,则需要先清除缓存
  • 2、实例化 bean,将 BeanDefinition 转换为 BeanWrapper
    • a、如果存在工厂方法,则使用工厂方法进行初始化
    • b、一个类有多个构造函数,每个构造函数都有不同的参数,所以苏姚根据参数锁定构造函数,并进行初始化
    • c、如果即不存在工厂方法,也不存在带有参数的构造函数,则使用默认构造函数进行 bean 实例化
  • 3、MergedBeanDefinitionPostProcessor 的应用
    • bean 合并后的处理,Autowired 注解正是通过此方法实现诸如类型的预解析
  • 4、依赖处理
    • 解决循环依赖问题(只对单例有效)
  • 5、属性填充,将所有属性填充至 bean 的实例中
  • 6、循环依赖检查
    • 对于 prototype 的 bean,Spring 没有好的解决办法,唯一要做的就是抛出异常,在这个步骤会检测已经加载的 bean 是否已经出现了循环依赖,并判断是否需要抛出异常
  • 7、注册 DisposableBean
    • 若配置了 destory-method,这里需要注册,以便于在销毁时调用
  • 8、完成创建并返回
    • 上面每一步骤都是用的大量的代码来完成其功能,最复杂的,同时也最难以理解的是循环依赖的处理

1、创建 bean 的实例

接下里我们来深入分析创建 bean 的每一个步骤,首先从 createBeanInstance 开始,代码如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) 
    // Make sure bean class is actually resolved at this point.
    //解析 class
    Class<?> beanClass = resolveBeanClass(mbd, beanName);

    if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) 
        throw new BeanCreationException(mbd.getResourceDescription()10Spring 源码学习 ~ Bean 的加载步骤详解

Spring源码解析——Bean加载(doCreateBean方法补充)

Spring源码解析——Bean加载(doCreateBean方法补充)

spring源码之bean加载(bean解析下篇)

Spring详解Spring中Bean的加载

Spring源码学习bean的加载