Spring IOC和循环依赖学习心得

版本:Spring 6 JDK 17

IOC(控制反转)

  • 什么是Bean:实现了某些功能的组件,将它定义为Bean,可以通过简洁的方法调用它从而实现想要的功能。

  • 什么是Spring:在讨论控制反转时,Spring可以被认为是一个IoC容器,用来创建,管理Bean的生命周期。

  • 什么是控制反转,控制了什么,反转了什么:

    • 控制了什么:由人为的使用new关键字创建对象改为IoC容器控制对象的创建和外部资源的获取。

    • 反转了什么:由人为的手动注入依赖改为由IoC容器查找并注入依赖。

  • IoC容器的输入

    • 以读取xml为例:

  • 解析配置后生成了什么:生成了BeanDefinition;BeanDefinition是一个接口,包含了这个类的信息。
    其中getBeanClassName() 和 isSingleton() 分别获取Bean类名和是否是单例;

  • IoC容器的类型和存放的内容是什么:将生成的BeanDefinition注册到IoC容器;IoC底层是一个名为beanDefinitionMap 的HashMap类型集合。
    在DefaultListableBeanFactory这个类里面

private final Map<String, BeanDefinition> beanDefinitionMap;
//在无参构造方法里面实例化
public DefaultListableBeanFactory() {
        ...省略
        this.beanDefinitionMap = new ConcurrentHashMap(256);

    }


在DefaultListableBeanFactory这个类里面

//这个方法负责将新的beanDefinition注册到IoC容器
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");
        if (beanDefinition instanceof AbstractBeanDefinition) {
          //验证 BeanDefinition 是否有效
        }
         //从beanDefinitionMap获取同名的beanDefinition
        BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
        //存在同名的beanDefinition判断是否允许覆盖
        if (existingDefinition != null) {
            ........
            //允许覆盖则将beanDefinition放到IoC容器
            this.beanDefinitionMap.put(beanName, beanDefinition);
        } else {
            //如果正在创建中则加锁
            if (this.hasBeanCreationStarted()) {
                synchronized(this.beanDefinitionMap) {
                    //将beanDefinition放到IoC容器
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    this.removeManualSingletonName(beanName);
                }
            } else {
                //将beanDefinition放到IoC容器
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.removeManualSingletonName(beanName);
            }

            this.frozenBeanDefinitionNames = null;
        }

       ...省略

    }

到这里,以配置文件的方式解析bean,并且注册到IoC容器中已经做好了;注册好的BeanDefinition信息已经可以使用,以后对Bean的操作都围绕着这个HashMap进行,是IoC控制反转的基础。

循环依赖

看到一句很有意思的话 “创建一个bean比生成一个BeanDefinition热闹多了

什么是循环依赖:A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
如下图:

解读

根据bean的生命周期,bean先开始实例化(创建对象),然后初始化(给属性赋值)。

在创建A的时候,A完成了实例化,在进行初始化时发现要给B赋值,但此时B还未创建,

于是进行B的创建,B完成了实例化,在进行初始化时发现要给A赋值,但此时A还未完全创建,

又开始进行A的创建,这个过程一直递归,最终什么也没创建出来。

// 异常circular reference(循环引用)
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Spring给出的解决方案:通过singletonFactories (三级缓存)提前曝光Bean

在DefaultSingletonBeanRegistry这个类里面定义了三种不同状态的缓存

  //一级缓存(singletonObjects):单例对象,已经实例化并且属性赋值,这里的对象是完整对象;
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
  //二级缓存(earlySingletonObjects):早期单例对象,已经实例化但尚未属性赋值,这里的对象是半成品对象;
  private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
  //三级缓存(singletonFactories ):单例工厂
  private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

在A实例化之后,初始化之前,Spring会提前”曝光“A,此时将A放入三级缓存(singletonFactories ),在初始化A的时候发现要给B赋值,于是就进行B的创建,在B完成实例化,在初始化之前Spring同样会曝光B,在初始化B的时候发现依赖A,这时A已经在三级缓存里面,于是能够拿到A的半成品,顺利的完成了B的初始化过程,将自身放入一级缓存。

A拿到B的对象后也顺利完成了自己的初始化过程,将自身放入一级缓存。

  • 为什么Spring能提前曝光Bean?:在上面IoC里面提到的BeanDefinition里面有个isSingleton()方法,用来判断当前bean是否是单例。
    对于单例对象,早曝光和晚曝光效果都是一样的。

核心类、方法、属性。

通过Debug

1.核心类:AbstractBeanFactory 核心方法:getBean、doGetBean

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
        String beanName = this.transformedBeanName(name);
        //先在IoC容器里面尝试获取Bean,具体会通过doGetBean方法去单例缓存Map里面获取
        Object sharedInstance = this.getSingleton(beanName);
        Object beanInstance;
        if (sharedInstance != null && args == null) {
            if (this.logger.isTraceEnabled()) {
                if (this.isSingletonCurrentlyInCreation(beanName)) {
                    this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
                } else {
                    this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }

            beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
        }

获取到了直接返回,获取不到进行下一步,创建Bean

2.核心类:AbstractAutowireCapableBeanFactory 核心方法:doCreateBean

bean的实例化和初始化正是在这个方法里面进行

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
        }

        if (instanceWrapper == null) {
            instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }
        //半成品bean
        Object bean = instanceWrapper.getWrappedInstance();
        Class<?> beanType = instanceWrapper.getWrappedClass();
        if (beanType != NullBean.class) {
            mbd.resolvedTargetType = beanType;
        }

        synchronized(mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                } catch (Throwable var17) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
                }

                mbd.postProcessed = true;
            }
        }
        //曝光的前期工作
        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");
            }
            //曝光bean
            this.addSingletonFactory(beanName, () -> {
                return this.getEarlyBeanReference(beanName, mbd, bean);
            });
        }

        Object exposedObject = bean;

        try {
            //实例化完成后,填充bean(即初始化)
            this.populateBean(beanName, mbd, instanceWrapper);
            exposedObject = this.initializeBean(beanName, exposedObject, mbd);
        } 

实例化的bean,已经有地址,但是还未赋值,这时已经可以曝光

3.曝光,核心类:DefaultSingletonBeanRegistry核心方法:addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized(this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            //将半成品bean放入三级缓存,完成曝光
            this.singletonFactories.put(beanName, singletonFactory);
            //从二级缓存中删除
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }

    }
}

Spring在什么情况下无法解决循环依赖

  • 构造函数注入,Spring 无法解决循环依赖问题:因为构造函数注入法在实例化的时候就需要所有完整的依赖对象,将实例化和初始化绑死在了一起。

  • scope="prototype",spring根本不会缓存此类bean,因为此类bean每次创建都是一个新的实例。

心得

  • 在了解IoC的时候重新认识了Spring的角色,它充当一个容器帮助开发者创建和管理对象,使得开发者更专注于业务逻辑的开发;控制反转作为一种思想,DI作为技术实现,指导开发者设计出松耦合,高扩展的程序。

  • 学习循环依赖的时候了解到了循环依赖的成因,当类之间有复杂的依赖关系时,容易形成闭环。spring通过三级缓存机制解决单例bean的循环依赖问题。

  • 在学习这两者的过程中,我并没有过早的沉溺于细节源码,而是一开始站在宏观的角度了解IoC的设计理念;再在研究源码的过程中抓住核心的类、方法、属性进行理解。

参考文章

  1. | Java 全栈知识体系 (pdai.tech)

  2. https://juejin.cn/post/6844903843596107790

  3. Spring6 (yuque.com)


Spring IOC和循环依赖学习心得
http://localhost:8090//archives/spring
作者
LinWJ
发布于
2024年09月15日
许可协议