首页 > 其他分享 >Spring的循环依赖

Spring的循环依赖

时间:2024-10-04 15:11:31浏览次数:12  
标签:缓存 Spring beanName 二级缓存 singletonObjects getBean bean 依赖 循环

1. 没有代理对象时的处理

解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,提前暴露引用,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为:

  • 步骤一:getBean(a),检查singletonObjects是否包含a,singletonObjects不包含a,实例化A放进singletonObjects,设置属性b,发现依赖B,尝试getBean(b)
  • 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含b,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a)
  • 步骤三:getBean(a),检查singletonObjects是否包含a,singletonObjects包含a,返回a
  • 步骤四:步骤二中的b拿到a,设置属性a,然后返回b
  • 步骤五:步骤一中的a拿到b,设置属性b,然后返回a

可见调整bean放进singletonObjects(人称一级缓存)的时机到bean实例化后即可解决循环依赖问题。但为了和spring保持一致,我们增加一个二级缓存earlySingletonObjects,在bean实例化后将bean放进earlySingletonObjects中(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行),getBean()时检查一级缓存singletonObjects和二级缓存earlySingletonObjects中是否包含该bean,包含则直接返回(见AbstractBeanFactory#getBean第1行)。

增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a。

Mini-Spring容器

import java.util.HashMap;
import java.util.Map;

public class MiniSpringContainer {
    // 一级缓存:存放完全初始化的单例bean
    private final Map<String, Object> singletonObjects = new HashMap<>();

    // 二级缓存:存放早期曝光的单例bean(未完成属性设置)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    // 模拟getBean方法
    public Object getBean(String beanName) {
        // 1. 先从一级缓存中获取已完成初始化的bean
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        }

        // 2. 再从二级缓存中获取早期曝光的bean
        if (earlySingletonObjects.containsKey(beanName)) {
            return earlySingletonObjects.get(beanName);
        }

        // 3. 如果bean未被创建,则创建bean
        Object bean = createBean(beanName);

        // 4. 将创建的bean放入一级缓存
        singletonObjects.put(beanName, bean);
        return bean;
    }

    // 模拟bean创建过程
    private Object createBean(String beanName) {
        Object bean = null;
        if ("A".equals(beanName)) {
            // 5. 创建A的早期对象,并放入二级缓存
            bean = new A();
            earlySingletonObjects.put(beanName, bean);

            // 6. 设置A的依赖B
            ((A) bean).setB((B) getBean("B"));
        } else if ("B".equals(beanName)) {
            // 5. 创建B的早期对象,并放入二级缓存
            bean = new B();
            earlySingletonObjects.put(beanName, bean);

            // 6. 设置B的依赖A
            ((B) bean).setA((A) getBean("A"));
        }

        // 7. 从二级缓存移除,放入一级缓存中
        earlySingletonObjects.remove(beanName);
        return bean;
    }
}

// A类和B类相互依赖
class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }
}

class B {
    private A a;
    public void setA(A a) {
        this.a = a;
    }
}

说明:

  1. getBean("A")被调用时,Spring会首先检查一级缓存singletonObjects是否存在A。如果不存在,进入createBean过程,开始创建A,并立即将A的早期对象(未初始化完全)放入二级缓存earlySingletonObjects中。
  2. A依赖B,故而createBean("B")被调用,B也会被放入二级缓存中。此时B依赖A,但由于A已被放入二级缓存,B可以拿到A的早期对象。
  3. 最后,A和B的依赖关系设置完成后,它们会被从二级缓存中移入一级缓存。

通过这种方式,Spring可以在实例化过程中打破循环依赖,因为依赖项是从二级缓存中取的早期对象。

2. 有代理对象时的处理

在有代理对象的情况下,Spring采用了三级缓存机制。在这种情况下,代理对象不能直接放到二级缓存,而是通过工厂方法来生成并放入三级缓存。这里简化代码来展示如何处理代理对象的循环依赖。

Mini-Spring容器(带代理对象)

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class MiniSpringContainerWithProxy {
    // 一级缓存:存放完全初始化的单例bean(包括代理对象)
    private final Map<String, Object> singletonObjects = new HashMap<>();

    // 二级缓存:存放早期曝光的单例bean(未完成属性设置,未代理)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    // 三级缓存:存放bean工厂,生成代理对象的工厂
    private final Map<String, Supplier<Object>> singletonFactories = new HashMap<>();

    // 模拟getBean方法
    public Object getBean(String beanName) {
        // 1. 先从一级缓存中获取已完成初始化的bean
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        }

        // 2. 再从二级缓存中获取早期曝光的bean
        if (earlySingletonObjects.containsKey(beanName)) {
            return earlySingletonObjects.get(beanName);
        }

        // 3. 从三级缓存中获取bean工厂,生成代理对象
        if (singletonFactories.containsKey(beanName)) {
            Object proxyBean = singletonFactories.get(beanName).get();
            earlySingletonObjects.put(beanName, proxyBean);
            return proxyBean;
        }

        // 4. 如果bean未被创建,则创建bean
        Object bean = createBean(beanName);

        // 5. 将创建的bean放入一级缓存
        singletonObjects.put(beanName, bean);
        return bean;
    }

    // 模拟bean创建过程
    private Object createBean(String beanName) {
        Object bean = null;
        if ("A".equals(beanName)) {
            // 创建A的实例,并放入三级缓存中,通过代理工厂暴露代理对象
            bean = new A();
            singletonFactories.put(beanName, () -> createProxy(bean));

            // 设置A的依赖B
            ((A) bean).setB((B) getBean("B"));
        } else if ("B".equals(beanName)) {
            // 创建B的实例,并放入三级缓存中,通过代理工厂暴露代理对象
            bean = new B();
            singletonFactories.put(beanName, () -> createProxy(bean));

            // 设置B的依赖A
            ((B) bean).setA((A) getBean("A"));
        }

        // 初始化完成后,从三级缓存移除,将bean放入一级缓存
        singletonFactories.remove(beanName);
        return bean;
    }

    // 模拟创建代理对象
    private Object createProxy(Object bean) {
        // 简化代理对象创建,实际中可能会用动态代理等方式
        return new ProxyBean(bean);
    }
}

// A类和B类相互依赖
class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }
}

class B {
    private A a;
    public void setA(A a) {
        this.a = a;
    }
}

// 简化的代理对象类
class ProxyBean {
    private final Object target;
    public ProxyBean(Object target) {
        this.target = target;
    }
}

说明:

  1. getBean("A")被调用时,Spring会创建A,并将A的代理工厂(Supplier<Object>)放入三级缓存中singletonFactories。这样,当B依赖A时,可以从三级缓存中获取A的代理对象。
  2. 同样,当B被创建时,其代理工厂也被放入三级缓存。
  3. 一旦代理对象被生成并注入到依赖链中,它会被移到二级缓存中供后续使用,最终完成初始化并移入一级缓存。

总结

  • 没有代理对象时:使用一级和二级缓存,通过提前曝光未完全初始化的bean来解决循环依赖。
  • 有代理对象时:通过三级缓存暴露代理对象工厂,以确保依赖链中获取到的是代理对象,解决代理bean的循环依赖。

整体流程

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class MiniSpringContainerWithObjectFactory {

    // 一级缓存:完全初始化的单例bean
    private final Map<String, Object> singletonObjects = new HashMap<>();

    // 二级缓存:提前曝光的单例bean,尚未完全初始化
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    // 三级缓存:ObjectFactory,用来生成bean实例(包括代理对象)
    private final Map<String, Supplier<Object>> singletonFactories = new HashMap<>();

    // 模拟getBean方法
    public Object getBean(String beanName) {
        // 1. 检查一级缓存
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        }

        // 2. 检查二级缓存(已曝光但未完全初始化的bean)
        if (earlySingletonObjects.containsKey(beanName)) {
            return earlySingletonObjects.get(beanName);
        }

        // 3. 检查三级缓存,通过ObjectFactory来生成代理或者实例化bean
        if (singletonFactories.containsKey(beanName)) {
            Object bean = singletonFactories.get(beanName).get();
            earlySingletonObjects.put(beanName, bean); // 将从三级缓存中取出的bean放入二级缓存
            return bean;
        }

        // 4. 如果没有缓存中的bean,则创建
        return createBean(beanName);
    }

    // 模拟bean的创建过程
    private Object createBean(String beanName) {
        Object bean;

        if ("A".equals(beanName)) {
            // 通过构造函数创建bean A,并将其ObjectFactory放入三级缓存中
            bean = new A();
            // 提前曝光bean A
            singletonFactories.put(beanName, () -> bean);

            // 开始填充A的依赖,依赖于B
            ((A) bean).setB((B) getBean("B"));
        } else if ("B".equals(beanName)) {
            // 通过构造函数创建bean B,并将其ObjectFactory放入三级缓存中
            bean = new B();
            // 提前曝光bean B
            singletonFactories.put(beanName, () -> bean);

            // 开始填充B的依赖,依赖于A
            ((B) bean).setA((A) getBean("A"));
        } else {
            throw new RuntimeException("Unknown bean: " + beanName);
        }

        // 完成bean的初始化,移除三级缓存中的ObjectFactory
        singletonFactories.remove(beanName);

        // 从二级缓存移到一级缓存
        earlySingletonObjects.remove(beanName);
        singletonObjects.put(beanName, bean);

        return bean;
    }
}

// 模拟相互依赖的A类和B类
class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }
}

class B {
    private A a;
    public void setA(A a) {
        this.a = a;
    }
}

流程解释

  1. 检查 A 是否在缓存中getBean("A")时,首先检查一级缓存singletonObjects,如果不存在,则继续查找。
  2. 通过构造函数创建 A:如果A不存在,则调用createBean("A")方法开始创建A。
    • 在这个过程中,将A的ObjectFactory提前曝光到三级缓存中singletonFactories,为后续可能的依赖解决提供支持。
  3. A 开始属性填充,依赖 B:A发现自己依赖B,于是开始getBean("B")
  4. 检查 B 是否在缓存中:同样的,首先检查B是否在缓存中(一级、二级和三级)。
  5. 通过构造函数创建 B:如果B不存在,则调用createBean("B")开始创建B,并将B的ObjectFactory提前曝光。
  6. B 依赖 A,检查 A 是否在缓存中:B的依赖A已经在三级缓存中被曝光,通过ObjectFactory获取到A的早期对象,避免了重复创建。
  7. 返回 A 并完成 B 的创建:B获得A之后继续其创建流程,最终完成并返回给A。
  8. A 完成创建:最终A的依赖B被成功设置,A也完成了初始化,至此A和B的创建完毕。

关键点

  • 提前曝光:通过ObjectFactory将未完全初始化的bean提前放入三级缓存,避免重复创建。
  • 三级缓存的使用:如果依赖的bean在初始化过程中被再次请求,Spring会从三级缓存中获取已经曝光的早期对象,而不会重复创建bean。
  • 依赖填充:在创建bean时,Spring会自动检测依赖项并使用getBean方法解决这些依赖,确保每个bean的依赖都能被正确填充。

标签:缓存,Spring,beanName,二级缓存,singletonObjects,getBean,bean,依赖,循环
From: https://www.cnblogs.com/clarencezzh/p/18446618

相关文章

  • 【SpringBoot】结合Redis实现缓存
    Redis经常用于缓存。接下来,我们以Springboot框架为例。实现一些Redis的基础操作,创建完SpingBoot项目后,具体步骤如下图:  pom中添加项目依赖<!--Redis缓存--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-re......
  • 古典舞在线交流:SpringBoot平台实现与优化
    第一章绪论1.1研究背景在当今的社会,可以说是信息技术的发展时代,在社会的方方面面无不涉及到各种信息的处理。信息是人们对客观世界的具体描述,是人们进行交流与联系的重要途径。人类社会就处在一个对信息进行有效合理的加工中。它将促进整个社会的发展。随着社会信息技术......
  • Spring源码(13)--AOP 连接点及通知的方法调用的源码
    AOP基础知识AOP基础知识,详情见:https://blog.csdn.net/sinat_32502451/article/details/142291052AOP源码,详情见:https://blog.csdn.net/sinat_32502451/article/details/142291110ProceedingJoinPoint在上面的示例中,运用到了ProceedingJoinPoint。ProceedingJoinPoint是......
  • Spring源码(14) -- Aop动态代理CglibAopProxy
    AOP基础知识AOP基础知识,详情见:https://blog.csdn.net/sinat_32502451/article/details/142291052AOP源码,详情见:https://blog.csdn.net/sinat_32502451/article/details/142291110AopProxyAopProxy接口是配置的AOP代理的委托接口,允许创建实际的代理对象。开箱即用的实现可......
  • Spring源码(15) -- Aop动态代理之 Enhancer
    Enhancer用途Enhancer(增强器)是标准Jdk动态代理的替代品,用于生成动态子类以启用方法拦截,还允许代理扩展具体的基类。原始且最通用的回调类型是MethodInterceptor(方法拦截器)。通常,每个Enhancer都会使用一个Callback回调,但可以使用callbackFilter控制每个方法使用哪......
  • SpringCloud入门(四)Ribbon负载均衡
    一、Ribbon负载均衡原理SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。  SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。如下图:  基本流程如下:-拦截我们的RestTemplate请求http://userservi......
  • SpringCloud入门(三)Eureka 注册中心
    一、Eureka注册中心简介假如我们的服务提供者user-service部署了多个实例,如图: 问题:-order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?-有多个user-service实例地址,order-service调用时该如何选择?-order-service如何得知某个user-service实例是......
  • 数据库系统------函数依赖与范式
    码属性和非码属性在讲函数依赖和范式前,我们需要了解码属性和非码属性码属性就是构成候选码的属性,非码属性就是不构成候选码的属性,主属性是指构成主码的属性候选码是能够确定关系R的最小属性集合,可以有多个,即多种匹配方法,超码就是一个候选码随便加上另一个属性,主码就是候选码中......
  • SpringBoot中,如何把自定义的yml配置文件中的内容和对应类的bean对象取出
    首先yml文件中的自定义配置如下login-type-config:types:k1:"yuheng0"k2:"yuheng1"我们有两个对应的类,yuheng0和yuheng1,他们都实现了say接口,并且重写了say方法。下面要通过请求k1k2获取yuheng0和yuheng1类的bean对象。注意,要记得加上Component注解......
  • springboot+vue专家库管理系统的设计与实现【开题+程序+论文】
    系统程序文件列表开题报告内容研究背景在当今知识密集型社会中,各领域专家资源成为推动科技进步、解决复杂问题的关键力量。随着信息化技术的飞速发展,如何高效管理、合理配置并利用专家资源,成为各行各业面临的重要课题。传统的人工管理方式已难以满足大规模、高效率的专家资......