全网最细致的spring源码分析(三):spring中的循环依赖问题与解决方法

一、什么是循环依赖

        循环依赖就是在多个bean中,相互持有对方,导致在创建的时候无法加载。不如:beanA引用了beanB,beanB又应用了beanC,beanC最后又引用回了beanA,成了一个无限的循环。循环依赖是对象与对象之间才会发生的,而方法之间的相互调用的情况,叫做循环调用,此招无解最终会因为方法之间调用过多导致内存溢出。

 

在代码中表现大概就是如下的情况:

public class BeanA {
    private BeanB beanB;

    public BeanB getBeanB() {return beanB;}

    public void setBeanB(BeanB beanB) {this.beanB = beanB;}
}

public class BeanB {
    private BeanC beanC;

    public BeanC getBeanC() {return beanC;}

    public void setBeanC(BeanC beanC) {this.beanC = beanC;}
}

public class BeanC {
    private BeanA beanA;

    public BeanA getBeanA() {return beanA;}

    public void setBeanA(BeanA beanA) {this.beanA = beanA;}
}

二、spring是如何解决循环依赖的问题

        实际spring将循环依赖细分为了三种,并且spring只能解决在setter下的循环依赖,而无法解决其他两种情况。

  • 构造器循环依赖

  • setter循环依赖

  • prototype作用域循环依赖

        在细看三者之前,回顾一下第一篇文章中我们在获取单例Bean的时候有接触到的三个容器。如果不记得了最好回头看一下,他们在获取单例bean的时候发挥的作用。

  • singletonFactories : 单例对象工厂的缓存

  • earlySingletonObjects :提前暴光的单例对象的缓存

  • singletonObjects:单例对象的缓存

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

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

private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

1.构造器循环依赖

        通过构造器注入构成的循环依赖是无法解决的,spring会通过抛出BeanCurrentlyInCreationException异常表示出现了循环依赖。

        spring容器将每一个正在创建的Bean的时候,就会将这个bean提前曝光出去,也就是将其标识存入earlySingletonObjects之中,这个bean在创建的过程中一个标识将一直存在于earlySingletonObjects之中,直到创建完成,才会从中删除。spring也是通过这个容器来发现是否出现了循环依赖,让在存入earlySingletonObjects缓存中的时候,发现该标识已经存在就可以判断出循环依赖发生了,自此抛出异常。创建步骤如下:

  1. beanA创建

  2. 去earlySingletonObjects中查看是否存在

  3. 如果没有就继续构造器中需要的beanB

  4. 准备好之后同样的去earlySingletonObjects查看一下

  5. 很好也没有,继续准备beanB需要的beanC

  6. beanC准备后之后,也需要再次到earlySingletonObjects中查看

  7. beanC也不再其中,根据代码准备beanA

  8. beanA完成之后,去看看beanA是否存在earlySingletonObjects容器之中

  9. 一看吓一跳,beanA已经在容器中准备工作了,赶紧抛出BeanCurrentlyInCreationException异常

 

2.setter循环依赖

        在setter注入的时候发生的依赖循环是可以被解决的,但只能解决单例情况的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚刚完成构造器但还没有进行注入的bean来完成的。通过提前暴露一个单例工厂让其他的bean可以引用到该Bean。可以通过setAllowCircularReferences来禁止循环引用。文章后面我们来简单的实现一下setter注入时的循环依赖解决方法,先摸清具体步骤:

  1. 创建beanA的时候先使用无参构造方法,并且暴露一个ObjectFactory用于返回一个创建中bean,依旧会去查看是否存在earlySingletonObjects之中,生成的工厂会被存放到singletonFactories之中。

  2. 继续创建beanB,和beanA的操一致

  3. 创建beanC之后,尝试通过setter注入beanA,这时候就会发现beanA依旧存在了,由于之前创建了beanA的ObjectFactory,可以通过这个工厂返回已经创建好的beanA。

 

3.prototype作用域循环依赖

        这个就比较简单了,spring在prototype作用域下是无法完成循环注入的,spring容器不会对存在prototype的bean进行缓存,也就无法向单例的情况下提前暴露创建中的bean来解决依赖问题。

 

三、模仿实现自己的循环依赖解决

以下代码转载自https://blog.csdn.net/cl534854121/article/details/82881910

1.创建两个相互依赖的对象

public class BeanA {
    private BeanB beanB;

    public BeanB getBeanB() {return beanB;}

    public void setBeanB(BeanB beanB) {this.beanB = beanB;}
}

public class BeanB {
    private BeanA beanA;

    public BeanA getBeanA() {return beanA;}

    public void setBeanA(BeanA beanA) {this.beanA = beanA;}
}

2.创建ObjectFactory

public class ObjectFactory {
    /**用于缓存正在初始化中的对象,同时作为提前暴露的缓存*/
    private static final Map<Class, Object> currentInitObjects = new ConcurrentHashMap<>();
    /**用于缓存初始化好的单例对象*/
    private static final Map<Class, Object> objects = new ConcurrentHashMap<>();
    /**
     * 获取对象,并设值对象属性。
     * 1. 不考虑并发问题,简单的示例
     * 2. 解决单例setter循环依赖
     */
    public <T> T getObject(Class<T> cls) {
        //如果已经初始化过直接返回
        if (objects.containsKey(cls)) {
            return (T) objects.get(cls);
        }
        try {
            T t;
            //1. 简单的使用构造函数创建对象,并提前暴露到currentInitObjects中
            t = cls.newInstance();
            //提前暴露到currentInitObjects中
            currentInitObjects.put(cls, t);
            //2. 解决依赖属性值
            resolveDependence(t, cls);
            //3. 放入单例缓存中
            objects.put(cls, t);
            return t;
        } catch (Exception e) {
            System.out.println("初始化对象失败:" + cls);
            return null;
        } finally {
            //4. 从正在初始化缓存中移除
            currentInitObjects.remove(cls);
        }
    }

    /**
     * 解决依赖属性设值.
     * @param object 对象
     * @param cls 对象class
     */
    private void resolveDependence(Object object, Class cls) {
        //获取对象的属性,并进行赋值,省去了复杂的判断,就认为是对象
        //1.获取所有属性
        Field[] fields = cls.getDeclaredFields();
        //2.循环处理属性值
        Arrays.stream(fields).forEach(field -> {
            field.setAccessible(true);
            //2.1 获取属性class属性
            Class fieldClass = field.getType();
            Object value;
            //2.2 判断是否已经初始化过
            if (objects.containsKey(fieldClass)) {
                value = objects.get(fieldClass);
            } else if (currentInitObjects.containsKey(fieldClass)) {
                //2.3 判断当前初始化的类中有没有这个属性.
                value = currentInitObjects.get(fieldClass);
            } else {
                //2.4 如果都没有,进行初始化
                value = getObject(fieldClass);
            }
            //3. 使用反射设置属性的值
            try {
                field.set(object, value);
            } catch (IllegalAccessException e) {
                System.out.println("设置对象属性失败:" + cls + "-" + field.getName());
            }
        });
    }
}

3.创建测试类

public class Client {
    public static void main(String[] args) {
        ObjectFactory factory = new ObjectFactory();
        ClassB classB = factory.getObject(ClassB.class);
        ClassA classA = factory.getObject(ClassA.class);
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值