一张图彻底搞懂Spring循环依赖

简介: 循环依赖就是指循环引用,是两个或多个Bean相互之间的持有对方的引用。BeanA 类依赖了Bean B类,同时Bean B类又依赖了Bean A类。这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖。Bean A类依赖了Bean B类,Bean B类依赖了Bean C类,Bean C类依赖了Bean A类,如此,也形成了一个依赖闭环。自己引用了自己,自己和自己形成了依赖关系。同样也是一个依赖闭环。

1 什么是循环依赖?

循环依赖就是指循环引用,是两个或多个Bean相互之间的持有对方的引用。

如下图所示:

image.png

BeanA 类依赖了Bean B类,同时Bean B类又依赖了Bean A类。这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖。同理,再如下图的情况:

image.png

上图中,Bean A类依赖了Bean B类,Bean B类依赖了Bean C类,Bean C类依赖了Bean A类,如此,也形成了一个依赖闭环。再比如:

image.png

上图中,自己引用了自己,自己和自己形成了依赖关系。同样也是一个依赖闭环。那么,如果出现此类循环依赖的情况,会出现什么问题呢?

2 循环依赖问题复现

2.1 定义依赖关系

我们继续扩展前面的内容,给ModifyService增加一个属性,代码如下:


@GPService
public class ModifyService implements IModifyService {
   
   

    @GPAutowired private QueryService queryService;

    ...

}

给QueryService增加一个属性,代码如下:


@GPService
@Slf4j
public class QueryService implements IQueryService {
   
   

    @GPAutowired private ModifyService modifyService;

    ...

}

如此,ModifyService依赖了QueryService,同时QueryService也依赖了ModifyService,形成了依赖闭环。那么这种情况下会出现什么问题呢?

2.2 问题复现

我们来运行调试一下之前的代码,在GPApplicationContext初始化后打上断点,我们来跟踪一下IoC容器里面的情况,如下图:

image.png

启动项目,我们发现只要是有循环依赖关系的属性并没有自动赋值,而没有循环依赖关系的属性均有自动赋值,如下图所示:

image.png

这种情况是怎么造成的呢?我们分析原因之后发现,因为,IoC容器对Bean的初始化是根据BeanDefinition循环迭代,有一定的顺序。这样,在执行依赖注入时,需要自动赋值的属性对应的对象有可能还没初始化,没有初始化也就没有对应的实例可以注入。于是,就出现我们看到的情况。

3 使用缓存解决循环依赖问题

image.png

3.1 定义缓存

具体代码如下:


// 循环依赖的标识---当前正在创建的实例bean
    private Set<String> singletonsCurrectlyInCreation = new HashSet<String>();

    //一级缓存
    private Map<String, Object> singletonObjects = new HashMap<String, Object>();

    // 二级缓存: 为了将成熟的bean和纯净的bean分离. 避免读取到不完整的bean.
private Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();

3.2 判断循环依赖

增加getSingleton()方法:


/**
     * 判断是否是循环引用的出口.
     * @param beanName
     * @return
     */
    private Object getSingleton(String beanName,GPBeanDefinition beanDefinition) {
   
   

        //先去一级缓存里拿,
        Object bean = singletonObjects.get(beanName);
        // 一级缓存中没有, 但是正在创建的bean标识中有, 说明是循环依赖
        if (bean == null && singletonsCurrentlyInCreation.contains(beanName)) {
   
   

            bean = earlySingletonObjects.get(beanName);
            // 如果二级缓存中没有, 就从三级缓存中拿
            if (bean == null) {
   
   
                // 从三级缓存中取
                Object object = instantiateBean(beanName,beanDefinition);

                // 然后将其放入到二级缓存中. 因为如果有多次依赖, 就去二级缓存中判断. 已经有了就不在再次创建了
                earlySingletonObjects.put(beanName, object);


            }
        }
        return bean;
    }

3.3 添加缓存

修改getBean()方法,在getBean()方法中添加如下代码:


         //Bean的实例化,DI是从而这个方法开始的
    public Object getBean(String beanName){
   
   

        //1、先拿到BeanDefinition配置信息
        GPBeanDefinition beanDefinition = regitry.beanDefinitionMap.get(beanName);

        // 增加一个出口. 判断实体类是否已经被加载过了
        Object singleton = getSingleton(beanName,beanDefinition);
        if (singleton != null) {
   
    return singleton; }

        // 标记bean正在创建
        if (!singletonsCurrentlyInCreation.contains(beanName)) {
   
   
            singletonsCurrentlyInCreation.add(beanName);
        }

        //2、反射实例化newInstance();
        Object instance = instantiateBean(beanName,beanDefinition);

        //放入一级缓存
        this.singletonObjects.put(beanName, instance);

        //3、封装成一个叫做BeanWrapper
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        //4、执行依赖注入
        populateBean(beanName,beanDefinition,beanWrapper);
        //5、保存到IoC容器
        factoryBeanInstanceCache.put(beanName,beanWrapper);

        return beanWrapper.getWrapperInstance();

        }

3.4 添加依赖注入

修改populateBean()方法,代码如下:


    private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {
   
   

        ...

            try {
   
   

                //ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
                field.set(instance,getBean(autowiredBeanName));
            } catch (IllegalAccessException e) {
   
   
                e.printStackTrace();
                continue;
            }
        ...

    }

4 循环依赖对AOP创建代理对象的影响

4.1 循环依赖下的代理对象创建过程

我们都知道Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说Spring最终给我们放进容器里面的是一个代理对象,而非原始对象。

这里我们结合循环依赖,再分析一下AOP代理对象的创建过程和最终放进容器内的动作,看如下代码:


@Service
public class MyServiceImpl implements MyService {
   
   
    @Autowired
    private MyService myService;

    @Transactional
    @Override
    public Object hello(Integer id) {
   
   
        return "service hello";
    }
}

此Service类使用到了事务,所以最终会生成一个JDK动态代理对象Proxy。刚好它又存在自己引用自己的循环依赖的情况。跟进到Spring创建Bean的源码部分,来看doCreateBean()方法:


protected Object doCreateBean( ... ){
   
   

        ...

        // 如果允许循环依赖,此处会添加一个ObjectFactory到三级缓存里面,以备创建对象并且提前暴露引用
        // 此处Tips:getEarlyBeanReference是后置处理器SmartInstantiationAwareBeanPostProcessor的一个方法,
        // 主要是保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象
        // AOP自动代理创建器此方法里会创建的代理对象

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        boolean earlySingletonExposure = (mbd.isSingleton() && 
                                                    this.allowCircularReferences && 
                                                    isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
   
    // 需要提前暴露(支持循环依赖),注册一个ObjectFactory到三级缓存
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // 如果发现自己被循环依赖,会执行上面的getEarlyBeanReference()方法,从而创建一个代理对象从三级缓存转移到二级缓存里
        // 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时可以知道exposedObject仍旧是原始对象    populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);

        // 经过这两大步后,exposedObject还是原始对象
        // 注意:此处是以事务的AOP为例
        // 因为事务的AOP自动代理创建器在getEarlyBeanReference()创建代理后,
     // initializeBean() 就不会再重复创建了,二选一,下面会有详细描述)

        ...

        // 循环依赖校验(非常重要)
        if (earlySingletonExposure) {
   
   
                // 前面讲到因为自己被循环依赖了,所以此时候代理对象还存放在二级缓存中
                // 因此,此处getSingleton(),就会把代理对象拿出来
                // 然后赋值给exposedObject对象并返回,最终被addSingleton()添加进一级缓存中
                // 这样就保证了我们容器里缓存的对象实际上是代理对象,而非原始对象

                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {
   
   

                        // 这个判断不可少(因为initializeBean()方法中给exposedObject对象重新赋过值,否则就是是两个不同的对象实例)
                        if (exposedObject == bean) {
   
                    
                                exposedObject = earlySingletonReference;
                        }
                }
                ...
        }

}

以上代码分析的是代理对象有自己存在循环依赖的情况,Spring用三级缓存很巧妙的进行解决了这个问题。

4.2 非循环依赖下的代理对象创建过程

如果自己并不存在循环依赖的情况,Spring的处理过程就稍微不同,继续跟进源码:


protected Object doCreateBean( ... ) {
   
   
        ...

        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

        ...

        // 此处注意,因为没有循环引用,所以上面getEarlyBeanReference()方法不会执行
        // 也就是说此时二级缓存里并不会存在
        populateBean(beanName, mbd, instanceWrapper);

        // 重点在此
        //AnnotationAwareAspectJAutoProxyCreator自动代理创建器此处的postProcessAfterInitialization()方法里,会给创建一个代理对象返回
        // 所以此部分执行完成后,exposedObject() 容器中缓存的已经是代理对象,不再是原始对象
     // 此时二级缓存里依旧无它,更别提一级缓存了
     exposedObject = initializeBean(beanName, exposedObject, mbd);

        ...

        // 循环依赖校验
        if (earlySingletonExposure) {
   
   
                // 前面讲到一级、二级缓存里都没有缓存,然后这里传参数是false,表示不从三级缓存中取值
                // 因此,此时earlySingletonReference = null ,并直接返回

                // 然后执行addSingleton()方法,由此可知,容器里最终存在的也还是代理对象

                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {
   
   
                        if (exposedObject == bean) {
   
    
                                exposedObject = earlySingletonReference;
                        }
                }
             ...
}

根据以上代码分析可知,只要用到代理,没有被循环引用的,最终存在Spring容器里缓存的仍旧是代理对象。如果我们关闭Spring容器的循环依赖,也就是把allowCircularReferences设值为false,那么会不会出现问题呢?先关闭循环依赖开关。


// 它用于关闭循环引用(关闭后只要有循环引用现象将报错)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
   
   

    @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
   
   

        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);

    }
}

关闭循环依赖后,上面代码中存在A、B循环依赖的情况,运行程序会出现如下异常:


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?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)

此处异常类型也是BeanCurrentlyInCreationException异常,但报错位置在DefaultSingletonBeanRegistry.beforeSingletonCreation

我们来分析一下,在实例化A后给其属性赋值时,Spring会去实例化B。B实例化完成后会继续给B属性赋值,由于我们关闭了循环依赖,所以不存在提前暴露引用。因此B无法直接拿到A的引用地址,只能又去创建A的实例。而此时我们知道A其实已经正在创建中了,不能再创建了。所有就出现了异常。对照演示代码,来分析一下程序运行过程:


@Service
public class MyServiceImpl implements MyService {
   
   

    // 因为关闭了循环依赖,所以此处不能再依赖自己
    // 但是MyService需要创建AOP代理对象
    //@Autowired
    //private MyService myService;

    @Transactional
    @Override
    public Object hello(Integer id) {
   
   
        return "service hello";
    }
}

其大致运行步骤如下:


protected Object doCreateBean( ... ) {
   
   

        // earlySingletonExposure = false  也就是Bean都不会提前暴露引用,因此不能被循环依赖

        boolean earlySingletonExposure = (mbd.isSingleton() && 
                                                    this.allowCircularReferences && 
                                                    isSingletonCurrentlyInCreation(beanName));
        ...

        populateBean(beanName, mbd, instanceWrapper);

        // 若是开启事务,此处会为原生Bean创建代理对象
        exposedObject = initializeBean(beanName, exposedObject, mbd);

        if (earlySingletonExposure) {
   
   
                ... 

                // 因为上面没有提前暴露代理对象,所以上面的代理对象exposedObject直接返回。

        }
}

由上面代码可知,即使关闭循环依赖开关,最终缓存到容器中的对象仍旧是代理对象,显然@Autowired给属性赋值的也一定是代理对象。

最后,以AbstractAutoProxyCreator为例看看自动代理创建器实现循环依赖代理对象的细节。

AbstractAutoProxyCreator是抽象类,它的三大实现子类InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴们应该比较熟悉,该抽象类实现了创建代理的动作:


// 该类实现了SmartInstantiationAwareBeanPostProcessor接口 ,通过getEarlyBeanReference()方法解决循环引用问题

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
   
   

    ...

    // 下面两个方法是自动代理创建器创建代理对象的唯二的两个节点:

    // 提前暴露代理对象的引用,在postProcessAfterInitialization之前执行
    // 创建好后放进缓存earlyProxyReferences中,注意此处value是原始Bean

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
   
   

            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            this.earlyProxyReferences.put(cacheKey, bean);
            return wrapIfNecessary(bean, beanName, cacheKey);

    }

    // 因为它会在getEarlyBeanReference之后执行,这个方法最重要的是下面的逻辑判断
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   
   

        if (bean != null) {
   
   

            Object cacheKey = getCacheKey(bean.getClass(), beanName);

            // 下面的remove()方法返回被移除的value,也就是原始Bean
            // 判断如果存在循环引用,也就是执行了上面的getEarlyBeanReference()方法,
            // 此时remove() 返回值肯定是原始对象

            // 若没有被循环引用,getEarlyBeanReference()不执行
            // 所以remove() 方法返回null,此时进入if执行逻辑,调用创建代理对象方法
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
   
   
                    return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;

    }
    ...
}

根据以上分析可得知,自动代理创建器它保证了代理对象只会被创建一次,而且支持循环依赖的自动注入的依旧是代理对象。由上面分析得出结论,在Spring容器中,不论是否存在循环依赖的情况,甚至关闭Spring容器的循环依赖功能,它对Spring AOP代理的创建流程有影响,但对结果是无影响的。也就是说Spring很好地屏蔽了容器中对象的创建细节,让使用者完全无感知。

关注微信公众号『 Tom弹架构 』回复“Spring”可获取完整源码。

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!

原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

相关文章
|
3天前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
185 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
29 1
|
2月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
40 4
|
3月前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
3月前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
3月前
|
Java Spring 容器
循环依赖难破解?Spring Boot神秘武器@RequiredArgsConstructor与@Lazy大显神通!
【8月更文挑战第29天】在Spring Boot应用中,循环依赖是一个常见问题。当两个或多个Bean相互依赖形成闭环时,Spring容器会陷入死循环。本文通过对比@RequiredArgsConstructor和@Lazy注解,探讨它们如何解决循环依赖问题。**@RequiredArgsConstructor**:通过Lombok生成包含final字段的构造函数,优先通过构造函数注入依赖,简化代码但可能导致构造函数复杂。**@Lazy**:延迟Bean的初始化,直到首次使用,打破创建顺序依赖,增加灵活性但可能影响性能。根据具体场景选择合适方案可有效解决循环依赖问题。
113 0
|
4月前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
3月前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
4月前
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决