zoukankan      html  css  js  c++  java
  • Spring IoC 循环依赖的处理

    前言

    本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本。因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析。

    本篇文章主要介绍 Spring IoC 是怎么解决循环依赖的问题的。

    正文

    什么是循环依赖

    循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如A引用B,B引用A,像下面伪代码所示:

    public class A {
        private B b;
        
        // 省略get和set方法...
    }
    
    public class B {
        private A a;
        
        // 省略get和set方法...
    }
    

    Spring 如何解决循环依赖

    Spring IoC 容器对循环依赖的处理有三种情况:

    1. 构造器循环依赖:此依赖 Spring 无法处理,直接抛出 BeanCurrentlylnCreationException 异常。
    2. 单例作用域下的 setter 循环依赖:此依赖 Spring 通过三级缓存来解决。
    3. 非单例的循环依赖:此依赖 Spring 无法处理,直接抛出 BeanCurrentlylnCreationException 异常。

    构造器循环依赖

    还是假设上面的A和B类是构造器循环依赖,如下所示:

    public class A {
        private B b;
        
        public A(B b) {
            this.b = b;
        }
        
        // 省略get和set方法...
    }
    
    public class B {
        private A a;
        
        public B(A a) {
            this.a = a;
        }
        
        // 省略get和set方法...
    }
    

    然后我们在 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.leisurexi.ioc.circular.reference.A" autowire="constructor" />
    
        <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="constructor" />
    
    </beans>
    

    那么我们在获取 A 时,首先会进入 doGetBean() 方法(该方法在Spring IoC bean 的加载中分析过),会进行到如下代码块:

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
        // 省略其它代码...
        
        // 如果 bean 的作用域是单例
        if (mbd.isSingleton()) {
            // 创建和注册单例 bean
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    // 创建 bean 实例
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            // 获取bean实例
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
        
        // 省略其它代码...
     
    }
    

    上面方法中的 getSingleton() 方法会判断是否是第一次创建该 bean,如果是第一次会先去创建 bean,也就是调用 ObjectFacotygetObject() 方法,即调用 createBean() 方法创建 bean 前,会先将当前正要创建的 bean 记录在缓存 singletonsCurrentlyInCreation 中。

    在创建A时发现依赖 B,便先去创建 B;B在创建时发现依赖A,此时A因为是通过构造函数创建,所以没创建完,便又去创建A,发现A存在于 singletonsCurrentlyInCreation,即正在创建中,便抛出 BeanCurrentlylnCreationException 异常。

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        // 加锁
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            // 一级缓存中不存在当前 bean,也就是当前 bean 第一次创建
            if (singletonObject == null) {
                // 如果当前正在销毁 singletons,抛出异常
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                // 创建单例 bean 之前的回调
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    // 获取 bean 实例
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                // 省略异常处理...
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    // 创建单例 bean 之后的回调
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    // 将 singletonObject 放入一级缓存,并从二级和三级缓存中移除
                    addSingleton(beanName, singletonObject);
                }
            }
            // 返回 bean 实例
            return singletonObject;
        }
    }
    
    // 单例 bean 创建前的回调方法,默认实现是将 beanName 加入到当前正在创建 bean 的缓存中,
    // 这样便可以对循环依赖进行检测
    protected void beforeSingletonCreation(String beanName) {
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }
    
    // 单例 bean 创建后的回调方法,默认实现是将 beanName 从当前正在创建 bean 的缓存中移除
    protected void afterSingletonCreation(String beanName) {
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
            throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
        }
    }
    
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            // 这边bean已经初始化完成了,放入一级缓存
            this.singletonObjects.put(beanName, singletonObject);
            // 移除三级缓存
            this.singletonFactories.remove(beanName);
            // 移除二级缓存
            this.earlySingletonObjects.remove(beanName);
            // 将 beanName 添加到已注册 bean 缓存中
            this.registeredSingletons.add(beanName);
        }
    }
    

    setter循环依赖

    还是假设上面的A和B类是 field 属性依赖注入循环依赖,如下所示:

    public class A {
        private B b;
        
        // 省略get和set方法...
    }
    
    public class B {
        private A a;
        
        // 省略get和set方法...
    }
    

    然后我们在 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.leisurexi.ioc.circular.reference.A" autowire="byType" />
    
        <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="byType" />
    
    </beans>
    

    Spring 在解决单例循环依赖时引入了三级缓存,如下所示:

    // 一级缓存,存储已经初始化完成的bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存,存储已经实例化完成的bean
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
    // 三级缓存,存储创建bean实例的ObjectFactory
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    // 按先后顺序记录已经注册的单例bean
    private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
    

    首先在创建A时,会进入到 doCreateBean() 方法(前面的流程可以查看Spring IoC bean 的创建一文),如下:

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
        // 获取bean的实例
        BeanWrapper instanceWrapper = null;
        if (instanceWrapper == null) {
            // 通过构造函数反射创建bean的实例,但是属性并未赋值
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        // 获取bean的实例
        final Object bean = instanceWrapper.getWrappedInstance();
        
        // 省略其它代码...
    
        // bean的作用域是单例 && 允许循环引用 && 当前bean正在创建中
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
        // 如果允许bean提前曝光
        if (earlySingletonExposure) {
            // 将beanName和ObjectFactory形成的key-value对放入singletonFactories缓存中
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        
        // 省略其它代码...
        
    }
    

    在调用 addSingletonFactory() 方法前A的实例已经创建出来了,只是还未进行属性赋值和初始化阶段,接下来将它放入了三级缓存中,如下:

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        // 加锁
        synchronized (this.singletonObjects) {
            // 如果一级缓存中不包含当前bean
            if (!this.singletonObjects.containsKey(beanName)) {
                // 将ObjectFactory放入三级缓存
                this.singletonFactories.put(beanName, singletonFactory);
                // 从二级缓存中移除
                this.earlySingletonObjects.remove(beanName);
                // 将beanName加入到已经注册过的单例bean缓存中
                this.registeredSingletons.add(beanName);
            }
        }
    }
    

    接下来A进行属性赋值阶段(会在后续文章中单独分析这个阶段),发现依赖B,便去获取B,发现B还没有被创建,所以走创建流程;在B进入属性赋值阶段时发现依赖A,就去调用 getBean() 方法获取A,此时会进入 getSingleton() 方法(该方法的调用流程在Spring IoC bean 的加载一文中分析过),如下:

    public Object getSingleton(String beanName) {
        // allowEarlyReference设置为true表示允许早期依赖
        return getSingleton(beanName, true);
    }
    
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 先从一级缓存中,检查单例缓存是否存在
        Object singletonObject = this.singletonObjects.get(beanName);
        // 如果为空,并且当前bean正在创建中,锁定全局变量进行处理
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                // 从二级缓存中获取
                singletonObject = this.earlySingletonObjects.get(beanName);
                // 二级缓存为空 && bean允许提前曝光
                if (singletonObject == null && allowEarlyReference) {
                    // 从三级缓存中获取bean对应的ObjectFactory
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        // 调用预先设定的getObject(),获取bean实例
                        singletonObject = singletonFactory.getObject();
                        // 放入到二级缓存中,并从三级缓存中删除
                        // 这时bean已经实例化完但还未初始化完
                        // 在该bean未初始化完时如果有别的bean引用该bean,可以直接从二级缓存中取出返回
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
    

    尝试一级缓存 singletonObjects (肯定没有,因为A还没初始化完全),尝试二级缓存 earlySingletonObjects(也没有),尝试三级缓存 singletonFactories,由于A通过 ObjectFactory 将自己提前曝光了,所以B能够通过 ObjectFactory.getObject() 拿到A对象(虽然A还没有初始化完全,但是总比没有好呀)。B拿到A后顺利创建并初始化完成,调用上面分析过的 addSingleton() 方法将自己放入一级缓存中。此时返回A中,A也能顺利拿到完全初始化的B进行后续的阶段,最后也将自己放入一级缓存中,并从二级和三级缓存中移除。

    过程图如下所示:

    非单例循环依赖

    对于非单例的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存,因此无法提前暴露一个创建中的 bean

    总结

    本文主要介绍了 Spring 对三种循环依赖的处理,其实还有一种字段循环依赖,比如 @Autowired 注解标注的字段,但它和 setter 循环依赖的解决方法一样,这里就没有多说。

    最后,我模仿 Spring 写了一个精简版,代码会持续更新。地址:https://github.com/leisurexi/tiny-spring

    参考

  • 相关阅读:
    Bootstrap Table
    XML CDATA识别“<,>”
    LigerUI之Grid使用详解(一)——显示数据 --分页
    Oracle中对XMLType的简单操作(extract、extractvalue...)
    Qt实现应用程序单实例运行--LocalServer方式
    Live m3u8播放3个文件自动停止问题
    markdown
    node.js 知识记录
    .NET 高级架构师 WEB架构师 ------时间 总结 专注
    .NET 高级架构师 WEB架构师 ------走正确的路
  • 原文地址:https://www.cnblogs.com/leisurexi/p/13199726.html
Copyright © 2011-2022 走看看