您现在的位置是:首页 >技术杂谈 >spring 循环依赖网站首页技术杂谈

spring 循环依赖

WZTTMoon 2024-09-12 00:01:02
简介spring 循环依赖

一、循环依赖

依赖
依赖
模块A
模块B
  1. 循环依赖,是一种常见代码结构,用于处理一些特定问题。我们常说处理“循环依赖”,实际指的是处理构建循环依赖时遇到的问题。
  2. 构建循环依赖失败的原因只有一个:无法构造“完整”的依赖对象。这里边的“完整”很重要,有的完整只要new出来就可以。像spring 的“完整”则包含两步,第一步,new 出来;第二步,初始化;哪一步失败都是不完整的。

二、spring 构建循环依赖失败的情况

完整的spring bean 需要二步构建出来,自然构建失败也出现在这两步中。

  1. new 时构建循环依赖失败

    @Component
    public class XunHuanA {
        private XunHuanB b;
        //a的构造依赖b
        public XunHuanA(XunHuanB b) {
            super();
            this.b = b;
        }
        @Component
        public static class XunHuanB {
            private XunHuanB a;
           //b的构造依赖a
            public XunHuanB(XunHuanB a) {
                super();
                this.a = a;
            }
    
        }
    }
    

    这种依赖没法解决,只能改变结构,所以应该尽量避免构造函数依赖,一般都会一种中间对象的方式代替上述结构,spring最常用的ObjectProvider。改成如下

    @Component
    public class XunHuanA {
        private ObjectProvider<XunHuanB> b;
    
        public XunHuanA(ObjectProvider<XunHuanB> b) {
            super();
            this.b = b;
        }
        
        public void test(){
           XunHuanB b=    b.getIfUnique()
        }
    
        @Component
        public static class XunHuanB {
            private XunHuanA a;
    
            public XunHuanB(XunHuanA a) {
                super();
                this.a = a;
            }
    
        }
    }
    

    XunHuanA 依赖ObjectProvider 来间接依赖B,ObjectProvider 可以从容器中获取XunHuanA对象,这里有个前提就是不能在构建XunHuanA的过程(构造函数 和 bean初始化)中使用ObjectProvider获取b。

  2. 初始化时构建循环依赖失败
    spring 的初始化bean有很多步骤,常见的像,实现InitializingBean接口bean会调用afterPropertiesSet()方法;BeanPostProcessor接口的实现bean会对所有bean进行一些处理。

    @Component
    public class XunHuanA implements InitializingBean {
        private ObjectProvider<XunHuanB> b;
        
        //因为依赖ObjectProvider,所以不会出现问题
        public XunHuanA(ObjectProvider<XunHuanB> b) {
            super();
            this.b = b;
        }
    
        @Component
        public static class XunHuanB {
            private XunHuanA a;
    
            public XunHuanB(XunHuanA a) {
                super();
                this.a = a;
            }
    
        }
        //这是bean的初始化阶段方法,这里边获取b会造成构建失败
        @Override
        public void afterPropertiesSet() throws Exception {
            b.getIfUnique();
        }
    
    }
    

    XunHuanA实现InitializingBean接口,spring 初始化bean会调用afterPropertiesSet()方法,其方法中使用了b(要实例化b),所以会失败

三、spring 如何构建循环依赖

spring 一个完整的bean要经历两个阶段,实例化和初始化,但是从语言层面说,一个对象被实例化后就已经可以使用(被注入)。初始化更多的是业务层面的要求。所以spring采取了一种“最终“完整性方案来构建循环依赖。“最终”指的是实例化之后,并不必须立刻初始化,可以先缓存起来,后面在初始化。这就类似于你在吃饭的时候,老板突然叫你去改一个非常紧急的bug,这时你可以先把饭放一旁,等解决完了在继续吃。反正最终吃完饭就可以了。

spring 依靠两个缓存来构建循环依赖

  1. Map<String, Object> earlySingletonObjects:存储了实例化但没初始化的bean
  2. Map<String, ObjectFactory<?>> singletonFactories:用于处理实例化了的对象可能需要提前进行必要的初始化的情况。简单点说,就是,被注入之前,要进行一些处理,不处理就不能用的情况。例如aop,对象实例化之后,在初始化阶段可能被aop生成代理对象,那么这时earlySingletonObjects存储的就不是我们想要的对象。

大概的流程如下:
对象实例化之后,会被ObjectFactory包裹,并放入singletonFactories中。然后在发生循环依赖的时候从earlySingletonObjects获取实例化的bean,如果不存在则singletonFactories获取ObjectFactory,并调用ObjectFactory.getObject()获取bean并放入singletonFactories,已备再次发生循环依赖时使用

四、spring 构建循环依赖的几种情景

  1. 像第二步部分示例1的这种构造函数依赖,是无法构建的,spring会抛出异常
  2. bean加载顺序也可能造成无法构建,例如如下代码
        @Component
        public class XunHuanC {
            @Autowired
            private XunHuanD d;
    
            public XunHuanC() {
                super();
            }
    
            @Component
            public static class XunHuanD {
                @Autowired
                private XunHuanC c;
    
                public XunHuanD(XunHuanC c) {
                    super();
                    this.c = c;
                }
    
            }
        }
    
    spring 会先构建XunHuanD,这会造成构建失败,因为D依赖C,C又依赖D,且D因为是构造函数依赖C,无法实例化。解决的方法只要让spring 先构建C就可以。代码如下
       @Component
       public class XunHuanC {
           @Autowired
           private XunHuanD d;
    
           public XunHuanC() {
               super();
           }
           @DependsOn("xunHuanC")
           @Component
           public static class XunHuanD {
               @Autowired
               private XunHuanC c;
    
               public XunHuanD(XunHuanC c) {
                   super();
                   this.c = c;
               }
    
           }
       }
    
    @DependsOn(“xunHuanC”) 可以让C先于D构建,C是无参构造函数,所以C可以实例化并存放在earlySingletonObjects中,当C在初始化阶段注入D的时候,D依赖C,可以从earlySingletonObjects获取C,然后成功构建。此时C就可以注入D,最终完成构建循环依赖。

五、总结

  1. spring 成功构建循环依赖的条件是:必须先构建能实例化的对象,在构建依赖对象。
  2. spring 构建循环依赖依靠:earlySingletonObjects缓存预先存储实例化还未初始化的对象
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。