美文网首页
Spring 源码(八)循环依赖源码解析

Spring 源码(八)循环依赖源码解析

作者: xiaolyuh | 来源:发表于2019-09-29 12:51 被阅读0次

    循环依赖是指两个或者多个Bean之前相互持有对方。在Spring中循环依赖一般有三种方式:

    1. 构造函数循环依赖
    2. setter方法循环依赖
    3. prototype 范围的依赖处理

    构造函数循环依赖

    在Spring中构造函数循环依赖是无法解决的,因为构造函数依赖其实是方法间循环调用的一种,会发生死循环。但是在Spring中会直接抛出BeanCurrentlyInCreationException异常。源码如下:

    // 在缓存中获取Bean,如果没有就创建Bean
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            // 在缓存中获取Bean
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // 判断容器是否正在销毁单实例Bean
                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标示放到Set集合,如果失败则抛出BeanCurrentlyInCreationException异常
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<Exception>();
                }
                try {
                    // 创建Bean实例
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                ...
                if (newSingleton) {
                    // 将Bean实例注册到singletonObjects容器中
                    addSingleton(beanName, singletonObject);
                }
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    
    protected void beforeSingletonCreation(String beanName) {
        // 将当前需要创建的Bean标示方法Set集合,如果失败则抛出BeanCurrentlyInCreationException异常
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }
    

    执行过程:

    1. 从缓存中获取Bean,如果没有则走创建Bean流程
    2. 判断容器是否正在销毁单实例Bean,如果是则不创建Bean
    3. 将当前需要创建的Bean标示(name)放入Set集合中(当前正在创建的Bean池),如果放入失败则抛出BeanCurrentlyInCreationException异常
    4. 创建Bean实例
    5. 将Bean实例注册到容器(放到缓存中)

    解决构造函数依赖主要是第3步实现的,Spring在容器创建的Bean的时候,会将Bean的标示(name)放到一个Set集合里面(当前正在创建的Bean池)。当在创建Bean的过程中,发现自已经在这个Set集合中时,就直接会抛出BeanCurrentlyInCreationException异常,而不会发生死循环。

    setter方法循环依赖

    @Service
    public class AService {
        @Autowired
        private BService bService;
    
        @Autowired
        public void setbService(BService bService) {
            this.bService = bService;
        }
    }
    

    这两种方式都算是setter方法依赖。我们创建单实例Bean的大致过程可以划分成三个阶段:

    1. 实例化 createBeanInstance(beanName, mbd, args);
    2. 设置属性值 populateBean(beanName, mbd, instanceWrapper);
    3. 初始化 initializeBean(beanName, exposedObject, mbd);

    对于Setter注入造成的循环依赖,Spring容器是在创建Bean第一步实例化后,就将Bean的引用提前暴露出来。通过提前暴露出一个单例工厂方法,从而使得其他Bean可以引用到该Bean。

    创建Bean时提前暴露刚完成第一步的Bean,源码如下:

    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            return 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);
            }
        }
    }
    

    自动装配过程中获取单实例Bean,源码如下:

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
    

    我们可以看到,在自动装配Bean的过程中,会去找三个缓存:

    1. singletonObjects:存放完成创建的Bean所有步骤的单实例Bean
    2. earlySingletonObjects:存放只完成了创建Bean的第一步,且是由单实例工厂创建的Bean
    3. singletonFactories:存放只完成了创建Bean的第一步后,提前暴露Bean的单实例工厂。

    这里为什么会用一个三级缓存呢,为啥不直接将只完成了创建Bean的第一步的Bean直接方到earlySingletonObjects缓存中呢?

    我们跟入getEarlyBeanReference(beanName, mbd, bean)我们可以发现,单实例工厂方法返回Bean的时候还执行了后置处理器的,在后置处理器中我们还可以对Bean进行一些特殊处理。如果我们直接将刚完成实例化的Bean放入earlySingletonObjects缓存中,那么失去对Bean进行特殊处理的机会。

    源码如下:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                    if (exposedObject == null) {
                        return null;
                    }
                }
            }
        }
        return exposedObject;
    }
    

    对于singleton作用域 bean ,可以通过setAllowCircularReferences(false);来禁用循环引用。

    prototype 范围的依赖处理

    对于prototype作用域 bean, Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存prototype作用域的 bean ,因此无法提前暴露一个创建中的 bean 示。

    源码

    https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases

    spring-boot-student-spring 工程

    参考

    《Spring源码深度解析》

    相关文章

      网友评论

          本文标题:Spring 源码(八)循环依赖源码解析

          本文链接:https://www.haomeiwen.com/subject/oiizuctx.html