手写spring ioc(二)

手写spring ioc(二)

本篇介绍

本篇主要解决属性之间的循环依赖的问题

循环依赖解决方案

在我们探讨解决方案之前先看一个基于ioc(一) 的循环依赖的例子,看看会发生什么

public class CBean {
    private ABean aBean;
}
public class Test {

    DefaultListableBeanFactory fac = new DefaultListableBeanFactory();

    @org.junit.jupiter.api.Test
    public void testIoc1() throws Exception {
        BeanDefinition bd1 = new GenericBeanDefinition();
        bd1.setBeanClass(CBean.class);
        List<PropertyValue> pvs1 = new ArrayList<>();
        pvs1.add(new PropertyValue("aBean",new BeanReference("aBean")));
        bd1.setPropertyValues(pvs1);
        fac.registerBeanDefinition("cBean",bd1);

        BeanDefinition bd = new GenericBeanDefinition();
        bd.setBeanClass(ABean.class);
        bd.setInitMethodName("init");
        List<PropertyValue> pvs = new ArrayList<>();
        pvs.add(new PropertyValue("name","lry"));
        pvs.add(new PropertyValue("age",20));
        pvs.add(new PropertyValue("cBean",new BeanReference("cBean")));
        bd.setPropertyValues(pvs);
        fac.registerBeanDefinition("aBean",bd);
        fac.preInstantiateSingletons();
        //上面都是系统做的,以后会支持注解扫描出类注册bd,提前实例化bean

        ABean aBean = (ABean) fac.getBean("aBean");
        aBean.doSth();
    }
}

ABean依赖cBean,CBean 依赖aBean ,不出意外的stackOverflow了
要想解决问题,必须得发现问题,定位问题,循环依赖亦是如此,我们得先定位到什么时候出现了循环依赖,我们可以定义一个singletonsCurrentlyInCreation set集合存储正在创建的spring beanName,何谓正在创建,即getBean到放到singletonObjects里之前都是正在创建,一旦getBean(beanName)时发现singletonsCurrentlyInCreation 中已经有此beanName 就说明出现了循环依赖,那么如何解决呢?

我们可以定义一个earlySingletonObjects,在DI之前 就把实例化好的对象put到这个早期单例池中,到时候去这个里面拿即可

doGetBean代码改为如下

protected Object doGetBean(String beanName) throws Exception {
        BeanDefinition bd = beanDefinitionMap.get(beanName);
        if(bd==null||bd.getBeanClass()==null){
            return null;
        }
        Object instance = null;
        //单例直接从单例池拿
        if(bd.isSingleton()){
            instance = getSingleton(beanName);
            if(instance!=null){
                return instance;
            }
            if(singletonsCurrentlyInCreation.contains(beanName)){
                throw new Exception(beanName + " 循环依赖!" + singletonsCurrentlyInCreation);
            }
            singletonsCurrentlyInCreation.add(beanName);
        }

        //决策构造器
        instance = createBeanInstance(bd);

        if(bd.isSingleton()) {
            earlySingletonObjects.put(beanName, instance);
        }

        //DI
        populateBean(bd,instance);

        //AOP代理 生命周期回调
        instance = initializeBean(bd,instance);

        //加入单例池
        if(bd.isSingleton()) {
            singletonObjects.put(beanName, instance);
            earlySingletonObjects.remove(beanName);
            singletonsCurrentlyInCreation.remove(beanName);
        }
        return instance;
    }


    private Object getSingleton(String beanName){
        //单例池中拿 第一级缓存
        Object singletonObject = this.singletonObjects.get(beanName);
        //单例池没有&&当前bean正在创建中
        if (singletonObject == null&&isBeanNameInUse(beanName)){
            //从早期单例池拿
            synchronized (this.singletonObjects){
                //early中拿 第二级缓存
                singletonObject = this.earlySingletonObjects.get(beanName);
            }
        }
        return singletonObject;
    }

总结

本篇较为简单,当然这是在没有aop的情况,有aop的情况会极其复杂,spring源码会在循环依赖中提前进行aop代理,这也是不得已为之,不然最后spring容器存储的ABean里的cBean属性就不是代理对象了,可以看这个例子

public static void main(String[] args) {
        ABean a = new ABean();
        CBean c = new CBean();
        a.cBean = c; // cBean aop代理前
        c = null;  // aop代理
        System.out.println(a);
        //a的属性cBean 并不会为null,即 a的属性cBean是一个未经aop代理的对象,所以spring对于循环依赖的情况不得不提前aop
        //个人理解,可能有偏差
    }

关于循环引用还有一个问题 :
假如先getBean(“aBean”),然后getBean(“cBean”) ,按照我们解决循环依赖的逻辑,可以画出下图

在这里插入图片描述

图其中第八步的意思是从早期单例池拿出一个未DI的ABean赋给CBean的aBean属性,那么这一步会有问题吗,即最终的CBean的属性aBean有值吗,测试代码表明是有的,下面再给出一个例子说明这点

public static void main(String[] args) {
        CBean cBean = new CBean();
        ABean aBean = new ABean();
        cBean.aBean = aBean;

        aBean.age = 20;
        aBean.name = "lry";
        System.out.println(cBean);
        //此时cBean的属性aBean是有属性age和name的
    }
发布了127 篇原创文章 · 获赞 68 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/LiuRenyou/article/details/103505136