面试题24:Spring循环依赖

Spring循环依赖:原理、问题与解决方案

一、循环依赖的定义

在Spring框架中,循环依赖是指两个或多个Bean之间相互依赖,形成一个闭环的情况。例如,BeanA依赖于BeanB,同时BeanB又依赖于BeanA。这种情况在通过setter方法注入或者@Autowired属性注入时较为常见。

代码示例

// BeanA类
public class BeanA {
    private BeanB beanB;
    //  setter方法注入BeanB
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

// BeanB类
public class BeanB {
    private BeanA beanA;
    //  setter方法注入BeanA
    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

二、Spring三级缓存机制

(一)缓存结构

  1. ​一级缓存​

    • 作用:存放完全初始化好的Bean实例。

    • 存储类型:ConcurrentHashMap类型的SingletonObjects

    • 意义:当需要获取一个已经完全初始化的Bean时,可以直接从一级缓存中获取,提高获取Bean的效率。

  2. ​二级缓存​

    • 作用:存放未完成属性注入的Bean实例。

    • 存储类型:HashMap类型的earlySingletonObjects

    • 意义:在Bean实例创建过程中,当实例已经创建但还未完成属性注入等操作时,先将这个“半成品”的Bean实例存放在二级缓存中,以应对可能出现的其他Bean对它的依赖。

  3. ​三级缓存​

    • 作用:存放Bean的ObjectFactory

    • 存储类型:HashMap类型的singletonFactories

    • 意义:ObjectFactory是一个用于懒加载Bean的接口,它可以在需要时提供Bean的实例,而不是立即创建和存储它。这一机制有助于解决setter方法和@Autowired属性注入的循环依赖问题。

(二)Bean创建流程与三级缓存的关系

  1. 当创建一个Bean(例如BeanA)时,首先会将BeanA的ObjectFactory放入三级缓存。

  2. 在BeanA实例创建完成后,会进行属性注入。如果在注入属性时发现需要BeanB,那么就会开始创建BeanB。

  3. 同样,创建BeanB时,也会将BeanB的ObjectFactory放入三级缓存。

  4. 在注入BeanB的属性时,如果发现需要BeanA,此时就可以从三级缓存中获取到BeanA的ObjectFactory,并通过它来获取BeanA的实例(这个实例可能是还未完全初始化的),然后将这个实例注入到BeanB中,之后再将这个实例放入二级缓存,继续完成BeanB的其他初始化操作。最后,将完全初始化后的BeanB实例放入一级缓存,再将BeanB注入到BeanA中,从而完成BeanA的初始化。

三、构造器注入的循环依赖问题

(一)问题产生的原因

  1. 三级缓存机制只有在对象实例已经创建(即构造方法执行完后)才能生效,从而将实例暴露到earlySingletonObjects中。

  2. 在使用构造器注入时,Spring必须先创建依赖对象才能调用构造器。例如,BeanA的构造器必须要BeanB的实例作为参数,而BeanB又等着BeanA的实例,这就导致Spring在创建BeanA时,由于需要BeanB的实例,但BeanB的创建又依赖于BeanA的实例,使得Spring根本无法创建任何一个Bean的实例,从而导致了死锁。

(二)解决方案:@Lazy注解

  1. ​原理​

    • 可以通过在构造器中对参数添加@Lazy注解,让Spring注入一个代理对象。这个代理对象并不会立即触发依赖Bean的真正初始化,而是在真正调用该依赖Bean的方法时才会触发其初始化。

  2. ​示例代码修改​

    • 对于BeanA的构造器进行修改:

    public class BeanA {
        private BeanB beanB;
        // 使用@Lazy注解
        @Autowired
        public BeanA(@Lazy BeanB beanB) {
            this.beanB = beanB;
        }
    }

四、其他相关问题与最佳实践

(一)其他解决方案探讨

  1. ​@PostConstruct注解​

    • 虽然不能直接解决构造器注入的循环依赖问题,但可以在Bean的初始化阶段进行一些操作,例如在@PostConstruct注解标注的方法中进行一些属性的设置或者资源的加载,这些操作可以在依赖注入完成之后进行。

  2. ​使用setter注入​

    • 在可能的情况下,推荐使用setter注入而非构造器注入。因为setter注入不会在实例创建时就需要依赖的Bean,而是在Bean实例创建完成后再进行属性注入,这样可以避免构造器注入带来的循环依赖问题。

(二)Bean作用域对循环依赖的影响

  1. 对于prototype作用域的Bean,由于其每次获取时都会创建新的实例,所以在处理循环依赖时相对复杂。如果出现循环依赖,可能会导致更多的问题,因为无法像单例Bean那样可以通过缓存机制来解决。

(三)最佳实践总结

  1. 在设计Bean之间的依赖关系时,尽量避免循环依赖的出现。如果无法避免,优先考虑使用setter注入或者字段注入(通过@Autowired)的方式。

  2. 如果必须使用构造器注入且出现了循环依赖问题,可以考虑使用@Lazy注解来解决。

  3. 在使用@PostConstruct注解时,要确保其标注的方法中的操作不会引入新的循环依赖或者影响Bean的正常初始化顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值