• Spring循环依赖问题——从源码画流程图




    循环依赖 在线流程图

    在这里插入图片描述



    关键代码

    从缓存中查询getSingleton()方法

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        // 一级缓存 二级缓存中取
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    // 加锁后再查询 重新取一遍
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }
    

    经过单例Bean判断和是否允许开启依赖注入判断后,往第三级缓存中存ObjectFactory函数式接口对象

    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");
        }
        // 循环依赖-添加到三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    

    初始化后将循环依赖产生的代理对象赋值给普通对象

    	if (earlySingletonExposure) {
            // 从二级缓存中取代理对象
    			Object earlySingletonReference = getSingleton(beanName, false);
    			if (earlySingletonReference != null) {
                    // 赋值给当前普通对象
    				if (exposedObject == bean) {
    					exposedObject = earlySingletonReference;
    				}
    				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
    					String[] dependentBeans = getDependentBeans(beanName);
    					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    					for (String dependentBean : dependentBeans) {
    						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    							actualDependentBeans.add(dependentBean);
    						}
    					}
    					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.");
    					}
    				}
    			}
    		}
    



    相关知识

    案例:A对象 与 B对象互相引用,A对象 与 C对象互相引用。



    为什么要使用二级缓存

    作用:

    • 二级缓存中存储的是不完整的早期的bean。
    • 二级缓存使不完整的bean和完整的bean分开存储,保证并发线程安全,提高性能

    假如不使用二级缓存,只有一级缓存的情况。一级缓存它想要打破循环的话就只能把半成品Bean存放在一级缓存中,这样创建B对象时就获取到一级缓存中的半成品A对象了,这样也就打破了对象互相引用的死循环。

    问题是多线程访问时,其他线程可能就拿到了A对象取使用了。

    解决方法是对整个getBean()方法加锁,这样的问题是锁的粒度太大了,而且想获取已经创建好了的对象也需要等待

    所以就加入了二级缓存,用二级缓存来降低锁的粒度,提升性能,



    为什么要使用三级缓存

    作用:

    • 解决循环依赖的死循环
    • 使用ObjectFactory函数式接口,提升bean创建过程扩展性,保证规范,代码职责单一性

    如果不使用三级缓存,同时A对象要进行AOP的话,此时就会出现依赖注入给B对象中的属性的A的普通对象,A经过创建过程之后存入单例池的却是代理对象

    如果想要解决上面的问题就需要在创建bean对象的过程中,在实例化之后,属性填充之前就进行AOP操作生成代理对象,并存入二级缓存中。

    这种方式就破坏了Bean创建过程的规范,同时还会出现循环依赖多次创建多次动态代理对象。

    所以解决方法是我只判断出现了循环依赖这种情况才对该bean提前进行AOP操作。这些操作代码我不能加在创建bean的各个小步骤中,为了保证代码职责单一性。所以便有了三级缓存来保存ObjectFactory函数式接口对象,在出现了循环依赖的情况下就去执行对应方法。



    只使用两个缓存的问题

    如果只使用一级和二级缓存,那么就需要考虑AOP问题,可能给对象B注入的是普通对象,存入单例池是的代理对象

    如果只使用一级和三级缓存,A对象与B对象互相引用,A对象与C对象互相引用。在执行行A对象就会创建两个AOP代理对象分别注入给B和C



    不能解决构造器循环依赖

    因为在创建Bean的总流程中,先进行实例化,完成之后才会去往三级存储中存入数据。此时实例化都没有完成,普通对象都没有创建成功,所以无法处理

    解决方法是使用@Lazy注解



    为什么多例bean不能解决循环依赖问题

    我们从源码中是可以发现,再往三级缓存中存数据前是进行了if判断的,只有单例bean才会加入到三级缓存中。

    其实解决循环依赖核心就是使用了一个Map,而这个map就相当于一个缓存。

    我们bean是单例的,而且注入是通过setter字段注入的,单例意味着只需要创建一次对象,后续就可以从缓存中取出来,字段注入意味着我们无需调用构造方法进行注入。

    • 如果是原型Bean,那么意味着每次都要去创建bean,无法利用缓存
    • 如果是构造方法注入,那么意味着需要调用构造方法注入,也无法利用缓存



    初始化后代理对象赋值给原始对象

    在bean的创建过程中,还有一个地方跟循环依赖有关。

    如果A是动态代理对象,通过三级缓存循环依赖之后,就会出现B.a = A的代理对象。但进行了初始化整个过程的A对象是普通对象,所以此时就需要把二级缓存中的代理对象赋值给BeanA。

    	if (earlySingletonExposure) {
            	// 1. 从二级缓存中取出代理对象
    			Object earlySingletonReference = getSingleton(beanName, false);
    			if (earlySingletonReference != null) {
                    // 2. 赋值给普通对象
                    // 这里会判断初始化过程中是否改变了当前对象,如果改变了则下面的if判断不成立
    				if (exposedObject == bean) {
    					exposedObject = earlySingletonReference;
    				}
    				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
    					String[] dependentBeans = getDependentBeans(beanName);
    					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    					for (String dependentBean : dependentBeans) {
    						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    							actualDependentBeans.add(dependentBean);
    						}
    					}
    					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.");
    					}
    				}
    			}
    		}
    
    • @Async注解会在初始化过程中创建动态代理对象,导致上面if判断中得到的代理对象和原始bean对象不一致。故而抛出异常
    • @Transactional注解也是代理对象,为什么不会报错?这是因为@Transaction注解的代理对象和AOP实际上是一个代理对象。

    在解决循环依赖时,我就通过了普通对象创建了一个代理对象,解决你在初始化过程中有改变了普通对象,那我之前创建的代理对象岂不是没用了。



    解决循环依赖

    如果关闭了循环依赖功能,如果有两种方式解决循环依赖

    1. 使用@Lazy注解加在属性上,代表Spring容器加载时注入,会临时注入一个代理对象,等真正使用的时候再通过代理对象去调用最终的getBean()方法
    2. 添加一个中间类, 中间类去依赖A\B, 然后让中间类去组织他们的依赖方法

    在这里插入图片描述



    SpringBoot开启循环依赖

    不建议开启,能产生循环依赖问题的代码本身就是一种不规范的设计。

    Spring作者都已表示可能会在后续版本去掉循环依赖支持。 除非你是把 Spring代码移植到SpringBoot ,可以考虑开启循环依赖已保证之前代码正常性。

    spring.main.allow-circular-references=true
    
  • 相关阅读:
    Django框架之模型层(一)
    SpringBoot粗浅分析
    【Django | allauth】登录_注册_邮箱验证_密码邮箱重置
    B/S架构,java源码,医院绩效管理系统,覆盖了医院绩效管理工作“PDCA”循环的全过程,支持二次开发
    使用HttpServlet和@WebServlet注解
    [word] 怎么把word表格里的字放在正中间? #职场发展#知识分享#知识分享
    jmeter性能测试步骤实战教程
    .NET餐厅管理系统sql数据帮助类执行单条SQL(插入、更新、删除)
    什么无线蓝牙耳机好用?双十一新手必看的蓝牙耳机推荐
    2022杭电多校八 1005-Ironforge(二分+区间暴力)
  • 原文地址:https://blog.csdn.net/qq_44027353/article/details/139938719