美文网首页技术干货
Spring中的循环依赖

Spring中的循环依赖

作者: BrightLoong | 来源:发表于2018-09-16 17:50 被阅读57次
    circle 阅读原文请访问我的博客BrightLoong's Blog

    什么是循环依赖

    循环依赖就是循环引用,在spring中,就是两个或者多个bean相互之间持有对方。如下图,ClassA引用ClassB,ClassB引用ClassC,ClassC又引用ClassA,最终它们形成了一个环,这就是循环依赖。

    circle

    Spring中的循环依赖

    spring中将循环依赖分成了3中情况,分别是:

    • 构造器循环依赖
    • prototype范围的依赖处理
    • setter循环依赖

    构造器循环依赖

    通过构造器注入构成的循环依赖,此依赖无法解决。在Spring中会抛出BeanCurrentlyInCreationException异常表示循环依赖。

    对于构造器注入构成的循环依赖,在创建ClassA的时候,构造器需要ClassB,然后去创建ClassB,在创建ClassB的时候发现需要ClassA,形成了一个死循环,无法完成创建。

    prototype范围的依赖处理

    对于prototype作用域的bean,spring容器无法完成依赖注入,因为spring不像缓存单例那样缓存prototype作用域的bean。

    setter循环依赖

    表示通过setter注入方式构成的循环依赖,spring通过提前暴露构造器注入但未完成其他步骤(如setter操作)的bean来完成setter注入造成的循环依赖。

    自己简单的用代码来展示spring解决单例setter循环依赖的方式,具体spring中如何解决感兴趣可以自己阅读源码。

    创建两个循环依赖的类,ClassA和ClassB。

    package io.github.brightloong.lab.spring.cyclicdependence;
    
    /**
     * @author BrightLoong
     * @date 2018/9/13 11:17
     * @description
     */
    public class ClassA {
    
        private ClassB classB;
    
        public ClassB getClassB() {
            return classB;
        }
    
        public void setClassB(ClassB classB) {
            this.classB = classB;
        }
    
        public void say() {
            System.out.println("I am ClassA");
        }
    }
    

    package io.github.brightloong.lab.spring.cyclicdependence;
    
    /**
     * @author BrightLoong
     * @date 2018/9/13 11:17
     * @description
     */
    public class ClassB {
    
        private ClassA classA;
    
        public ClassA getClassA() {
            return classA;
        }
    
        public void setClassA(ClassA classA) {
            this.classA = classA;
        }
    
        public void say() {
            System.out.println("I am ClassB. Who are you?");
            classA.say();
        }
    }
    

    ObjectFactory用来模仿Spring解决循环依赖获取bean

    package io.github.brightloong.lab.spring.cyclicdependence;
    
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @author BrightLoong
     * @date 2018/9/13 11:19
     * @description
     */
    public class ObjectFactory {
    
        /**用于缓存正在初始化中的对象,同时作为提前暴露的缓存*/
        private static final Map<Class, Object> currentInitObjects = new ConcurrentHashMap<>();
    
        /**用于缓存初始化好的单例对象*/
        private static final Map<Class, Object> objects = new ConcurrentHashMap<>();
    
        /**
         * 获取对象,并设值对象属性。
         * 1. 不考虑并发问题,简单的示例
         * 2. 解决单例setter循环依赖
         *
         * @param cls
         * @param <T>
         * @return
         */
        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());
                }
            });
        }
    }
    

    客户端调用

    package io.github.brightloong.lab.spring.cyclicdependence;
    
    /**
     * @author BrightLoong
     * @date 2018/9/13 11:19
     * @description
     */
    public class Client {
    
        public static void main(String[] args) {
            ObjectFactory factory = new ObjectFactory();
            ClassB classB = factory.getObject(ClassB.class);
            classB.say();
            System.out.println("-----我是分割线-----");
    
            ClassA classA = factory.getObject(ClassA.class);
            classA.say();
            System.out.println("classB.getClassA() == classA:" + (classB.getClassA() == classA));
    
            System.out.println("classA.getClassB() == classB:" + (classA.getClassB() == classB));
        }
    }
    

    输出如下:

    I am ClassB. Who are you?
    I am ClassA
    -----我是分割线-----
    I am ClassA
    classB.getClassA() == classA:true
    classA.getClassB() == classB:true
    

    从输出可以发现:

    • ClassA和ClassB都成功实例化
    • 都是单例

    相关文章

      网友评论

      • Java耕耘者:你好!我们是Java耕耘者专注于程序员Java开发公众号“Java这点事”。我们很赞赏你的文章,希望能获得转载授权。授权后,你的文章将会在公众号“Java这点事”、发布。我们会注明来源和作者姓名。
        非常感谢~~~
        Java耕耘者:@BrightLoong 好的 谢谢
        BrightLoong:@Java耕耘者 非常荣幸能被赞赏:blush::blush:,可以转载的

      本文标题:Spring中的循环依赖

      本文链接:https://www.haomeiwen.com/subject/gcsfnftx.html