spring 循环依赖

文章详细讨论了Spring框架下循环依赖的问题,包括为什么会出现构建循环依赖失败的情况,以及Spring如何通过早期singleton对象和ObjectFactory实现循环依赖的构建。提到了构造函数依赖应避免,推荐使用ObjectProvider作为中介,并举例说明了@DependsOn注解在解决依赖顺序问题上的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、循环依赖
依赖
依赖
模块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缓存预先存储实例化还未初始化的对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值