循环依赖及spring三级缓存

1.循环依赖

目录

1.循环依赖

1.1 概念

 1.2 循环依赖的N种场景

2.模拟一级缓存

2.1 为了更好的解释使用缓存解决循环依赖问题,我们把创建bean的过程简化为以下三步:

2.2 创建流程(A和B相互依赖)

2.3 创建流程图:

 2.4 代码实现

2.5 发现问题

3.模拟二级缓存

3.1 代码实现

3.2 发现问题

3.3 ObjectFactory -- 对象工厂

4.spring三级缓存

4.1 bean的创建过程(丰富之前的创建流程)

4.2  三级缓存存储结构 - 源码

 4.3 getSingleton方法 - 源码截图

4.4 addSingletonFactory方法 - 源码截图

4.5 三级缓存对应的缓存内容

 4.6 A和B相互依赖,在spring(三级缓存)中的创建流程图

 4.7 思考


个人学习记录,希望对大家有帮助,有问题畅所欲言

1.循环依赖

1.1 概念

循环依赖:简单来说是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用

 1.2 循环依赖的N种场景

 1.2.1 单例的setter注入(singleton) -- 可解决的

后面会重点讲解,spring三级缓存如何解决此场景的循环依赖

 1.2.2 多例的setter注入(prototype) -- 不可解决的

a. @Scope注解,定义bean在springIoc容器中的作用域

b. prototype,原型模式,每次从容器中获取bean时,会新建一个实例,由请求方负责后继生命周期的管理,包括     销毁

c. 不可解决原因:每次都会创建新的对象,不会在缓存中存储,交给请求方后就会失去对此对象的索引

 1.2.3 构造器注入 -- 不可解决

 原因:创建实例完成之前,就需要去获取依赖的对象,此时半成品的bean未加入到缓存中,导致依赖无限循环下去

创建流程

 

2.模拟一级缓存

2.1 为了更好的解释使用缓存解决循环依赖问题,我们把创建bean的过程简化为以下三步:

a.实例化:创建出一个属性未赋值的对象

b.填充属性:给对象的属性赋值

c.初始化:执行init-Method

2.2 创建流程(A和B相互依赖)

        创建A

        1. 实例化A,将未填充属性的 半成品的a对象放入缓存中(a不完整,b=null)

        2. 填充属性b

        创建B:

        3. 实例化B,将未填充属性的 半成品的b对象放入缓存中(b不完整,a=null)

        4. 填充属性a,从缓存中获取了a(a不完整,b=null) 返回b

        5. A中填充b

        A创建完成

2.3 创建流程图:

 2.4 代码实现

InstanceA

//创建A类,属性依赖B
public class InstanceA {
    @Autowired
    InstanceB instanceB;
    public InstanceA(){
    }
    public void say(){
        System.out.println("hello world");
    }
}

 InstanceB

//创建B类,属性依赖A
public class InstanceB {
    @Autowired
    InstanceA instanceA;
    public InstanceB(){
    }
}

模拟一级缓存的类:One

/**
 * 通过一个缓存,尝试解决循环依赖问题
 * BeanDefinition 用于保存 Bean 的相关信息,包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等
 * 它是实例化 Bean 的原材料,Spring 就是根据 BeanDefinition 中的信息实例化 Bean
 */
public class One {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public static void loadBeanDefinition(){
        BeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        BeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA",aBeanDefinition);
        beanDefinitionMap.put("instanceB",bBeanDefinition);
    }
    public static void main(String[] args) throws Exception{
        loadBeanDefinition();
        InstanceA instanceA = (InstanceA)getBean("instanceA");
        instanceA.say();
    }

    //一级缓存
    public static Map<String,Object> singletonObjects = new ConcurrentHashMap<>();

    private static Object getBean(String beanName) throws InstantiationException, IllegalAccessException {
        //从缓存中获取
        Object singleton = getSingleton(beanName);
        if(singleton != null){
            return singleton;
        }
        //实例化 通过反射创建对象
        RootBeanDefinition beanDefinition = (RootBeanDefinition)beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        Object instance = beanClass.newInstance();
        //添加到一级缓存中 对象此时没有属性赋值 半成品的bean
        singletonObjects.put(beanName,instance);
        //填充属性
        Field[] declaredFields = beanClass.getDeclaredFields();
        for(Field declaredField : declaredFields){
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            //如果属性被Autowired注解修饰
            if(annotation != null){
                declaredField.setAccessible(true);
                //获取属性名
                String name = declaredField.getName();
                //创建需要依赖的属性对象
                Object fieldObject = getBean(name);
                declaredField.set(instance,fieldObject);
            }
        }
        //初始化 init-method
        return instance;
    }

    private static Object getSingleton(String beanName) {
        if(singletonObjects.containsKey(beanName)){
            return singletonObjects.get(beanName);
        }
        return null;
    }
}

2.5 发现问题

如何保证创建出的完整的bean是单例的???

解决:再用一个map用来缓存完整的bean,beanName作为key值,保证每一个名字可以获取到同一个实例bean

插曲:单例bean和单例模式的区别

①单例bean,是指spring容器中bean的一种作用域,一个名字对应一个实例bean

②单例模式是设计模式的一种,一个类只有一个私有的实例,对外提供了一个公共的访问入口

③区别:     单例bean,一个类可以对应多个实例     单例模式,一个类只有一个实例

3.模拟二级缓存

3.1 代码实现

模拟二级缓存的类:Two

/**
 * 模拟二级缓存
 */
public class Two {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public static void loadBeanDefinition(){
        BeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        BeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA",aBeanDefinition);
        beanDefinitionMap.put("instanceB",bBeanDefinition);
    }
    public static void main(String[] args) throws Exception{
        loadBeanDefinition();
        InstanceA instanceA = (InstanceA)getBean("instanceA");
        instanceA.say();
    }
    //一级缓存
    public static Map<String,Object> singletonObjects = new ConcurrentHashMap<>();
    //二级缓存
    public static Map<String,Object> earlySingletonObjects = new ConcurrentHashMap<>();

    private static Object getBean(String beanName) throws InstantiationException, IllegalAccessException {
        //出口
        Object singleton = getSingleton(beanName);
        if(singleton != null){
            return singleton;
        }
        //1.实例化 通过反射创建对象
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        Object instance = beanClass.newInstance();
        earlySingletonObjects.put(beanName,instance);
        //2.属性赋值
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField : declaredFields){
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if(annotation != null){
                declaredField.setAccessible(true);
                String name = declaredField.getName();
                Object fieldObject = getBean(name);
                declaredField.set(instance,fieldObject);
            }
        }
        //3.初始化 init-method
        //添加到一级缓存中
        singletonObjects.put(beanName,instance);
        //判断二级缓存中有没有该名字的半成品,如果有,就移除
        if(earlySingletonObjects.containsKey(beanName)){
            earlySingletonObjects.remove(beanName);
        }
        return instance;
    }

    private static Object getSingleton(String beanName) {
        //判断一级缓存有没有
        if(singletonObjects.containsKey(beanName)){
            return singletonObjects.get(beanName);
            //判断二级缓存有没有
        }else if(earlySingletonObjects.containsKey(beanName)){
            return earlySingletonObjects.get(beanName);
        }else{
            return null;
        }
    }
}

3.2 发现问题

如果被创建的类需要被增强,即需要进行AOP,获取到一个代理对象,此时只靠 二级缓存是无法满足的

解决

        ①用三级缓存map,存储ObjectFactory

        ②进行AOP的三个条件,原始对象(实例化之后的对象)、beanName、BeanDefinition

        ③三个参数以lambda表达式的方式存储在ObjectFactory中,通过getObject()方法获取     需要的实例(方法内会判断是否需要AOP,需要的话就创建代理对象,不需要的话就返回     原始对象,即半成品的bean)

3.3 ObjectFactory -- 对象工厂

//三级缓存 /** Cache of singleton factories: bean name to ObjectFactory. */

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

通过下图,我们可以看出,ObjectFactory对象传入的是一个lambda表达式,里面有三个参数,分别是:beanName(实例名)、mbd(BeanDefinition)、bean(未属性赋值的bean)

4.spring三级缓存

4.1 bean的创建过程(丰富之前的创建流程)

class(UserService) --> 推断构造方法 --> 实例化 --> 对象 --> 属性填充 --> afterPropertiesSet(初始化 调用initMethod) --> AOP --> 代理对象 --> bean

4.2  三级缓存存储结构 - 源码

DefaultSingletonBeanRegistry.java

//一级缓存
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

源码截图

 4.3 getSingleton方法 - 源码截图

spring源码的bean包中: DefaultSingletonBeanRegistry.java

① isSingletonCurrentlyInCreation() 判断当前单例bean是否正在创建中

② allowEarlyReference: 是否允许从singletonFactories中 通过getObject拿到对象

4.4 addSingletonFactory方法 - 源码截图

spring源码的bean包中: DefaultSingletonBeanRegistry.java

 在执行完构造方法之后,完成实例化,创建出 半成品的bean之后,将lambda表达式缓存在三级缓存中,此时,调用此方法

4.5 三级缓存对应的缓存内容

 4.6 A和B相互依赖,在spring(三级缓存)中的创建流程图

 4.7 思考

4.7.1 上图中,A创建的第8步是为什么,是否可以忽略??

:     

①第8步会判断是否进行了提前AOP,因为出现循环依赖的话,AOP生成代理对象 会比正常创建过程提前     

②如果省略第8步,即使二级缓存中是A的代理对象,保存在一级缓存中的也只能 是完整的原始对象     

③归根结底,对象创建完之后,一定要从缓存中拿出,放在一级缓存中,然后 移除二、三级缓存

4.7.2 为什么一级缓存是ConcurrentHashMap,二、三级缓存是HashMap??

答:

①一级缓存为了让外界获取被spring容器管理的bean,可能会有多个线程同时获取, 所以要用ConcurrentHashMap来保证线程安全     

②二、三级缓存的put和remove操作绑定在一起,并且被synchronized同步锁修饰, 已经保证线程安全,所以要用速度更快、线程不安全的HashMap

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值