Spring 是如何解决循环依赖的?你真的能说清楚吗?

前言

在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的。这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过spring的源码。但是说实话,spring的源码其实非常复杂的,研究起来并不是个简单的事情,所以我们此篇文章只是为了解释清楚Spring是如何解决循环依赖的这个问题。

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
图片

循环依赖有哪几种方式?

setter方式 - 单例

/**
 * A服务类
 *
 * @author liudong
 * @date 2021/12/20 14:48
 */
@Service("aService")
public class AService {
    @Autowired
    public BService bService;
}
/**
 * B服务类
 *
 * @author liudong
 * @date 2021/12/20 14:54
 */
@Service("bService")
public class BService {
    @Autowired
    public AService aService;
}
/**
 * 配置类
 *
 * @author liudong
 * @date 2021/12/20 14:59
 */
@Configuration
@ComponentScan
public class AppConfig {

}
/**
 * 测试应用类
 *
 * @author liudong
 * @date 2021/12/20 14:56
 */
public class TestApplication {
    public static void main(String[] args) {
        ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        AService aService = (AService) annotationConfigApplicationContext.getBean("aService");
        System.out.println(aService.bService);

        BService bService = (BService) annotationConfigApplicationContext.getBean("bService");
        System.out.println(bService.aService);

    }
}

执行结果:

com.gaodun.spring.BService@1ed6388a
com.gaodun.spring.AService@5a45133e

虽然AService和BService产生了循环依赖的关系,但是没有异常抛出。说明被Spring解决了。但是能够通过“setAllowCircularReferences(false)”来禁用循环引用。这样也会抛出BeanCurrentlyInCreationException异常!测试代码如下:

/**
 * 测试应用类
 *
 * @author liudong
 * @date 2021/12/20 14:56
 */
public class TestApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.setAllowCircularReferences(false);
        applicationContext.register(AppConfig.class);
        applicationContext.refresh();

        AService aService = (AService) applicationContext.getBean("aService");
        System.out.println(aService.bService);

        BService bService = (BService) applicationContext.getBean("bService");
        System.out.println(bService.aService);
    }
}

执行结果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService': Requested bean is currently in creation: Is there an unresolvable circular reference?

需要注意的是,我们不能这么写:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.setAllowCircularReferences(false);

如果你这么写,程序执行完第一行代码,整个Spring容器已经初始化完成了,你再设置不允许循环依赖,也于事无补了。

setter方式 - prototype

使用@Scope(BeanDefinition.SCOPE_PROTOTYPE),进行多例的setter注入 ,每次请求都会建立一个实例对象。

/**
 * A服务类
 *
 * @author liudong
 * @date 2021/12/20 14:48
 */
@Service("aService")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AService {
    @Autowired
    public BService bService;
}
/**
 * B服务类
 *
 * @author liudong
 * @date 2021/12/20 14:54
 */
@Service("bService")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BService {
    @Autowired
    public AService aService;
}

执行结果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService': Requested bean is currently in creation: Is there an unresolvable circular reference?

对于“prototype”作用域bean,Spring容器没法完成依赖注入,由于Spring容器不进行缓存“prototype”做用域的bean,所以没法提早暴露一个建立中的bean。

构造器循环依赖

/**
 * A服务类
 *
 * @author liudong
 * @date 2021/12/20 14:48
 */
@Service("aService")
public class AService {
    public BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }
}
/**
 * B服务类
 *
 * @author liudong
 * @date 2021/12/20 14:54
 */
@Service("bService")
public class BService {
    public AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}

执行结果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService': Requested bean is currently in creation: Is there an unresolvable circular reference?

通过构造器注入构成的循环依赖是没法解决的,由于加入singletonFactories三级缓存的前提是执行了构造器,因此构造器的循环依赖无法解决,只能抛异常BeanCurrentlyInCreationException异常表示循环异常。

Spring是如何解决循环依赖的?

根据上文所述,Spring通过setter方式单例注入,是可以解决循环依赖的。关键源码如下:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    // ...

    /**
    *一级缓存:单例池
    *存放已经初始化的bean——成品
    */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

    /**
    *三级缓存:单例工厂的高速缓存
    *存放生成bean的工厂
    */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

    /**
    *二级缓存:早期单例对象的高速缓存
    *存放已经实例化但未初始化(未填充属性)的的bean——半成品
    */
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);

    //...

    /**
    *
    *此处就是解决循环依赖的关键,这段代码发生在createBeanInstance以后,
    *也就是说单例对象此时已经被建立出来的。这个对象已经被生产出来了,
    *虽然还不完美(尚未进行初始化的第二步和第三步),
    *可是已经能被人认出来了(根据对象引用能定位到堆中的对象),
    *因此Spring此时将这个对象提早曝光出来让你们认识,让你们使用。
    */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized(this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }

        }
    }

    // ...

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //首先去一级缓存中获取如果获取的到说明bean已经存在,直接返回
        Object singletonObject = this.singletonObjects.get(beanName);
        //如果一级缓存中不存在,则去判断该bean是否在创建中,如果该bean正在创建中,就说明了,这个时候发生了循环依赖
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
                //如果发生循环依赖,首先去二级缓存中获取,如果获取到则返回,这个地方就是获取aop增强以后的bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                //如果二级缓存中不存在,且允许提前访问三级引用
                if (singletonObject == null && allowEarlyReference) {
                    //去三级缓存中获取
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        //如果三级缓存中的lambda表达式存在,执行aop,获取增强以后的对象,为了防止重复aop,将三级缓存删除,升级到二级缓存中
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }
    // ...

}

以上便是spring解决循环依赖的核心代码,那么对于:A对象setter依赖B对象,B对象setter依赖A对象,Spring解决循环依赖的详细步骤如下:

  • (1) A首先完成了初始化的第一步,而且将本身提早曝光到singletonFactories中。
  • (2) 此时进行初始化的第二步,发现本身依赖对象B,此时就尝试去get(B),发现B尚未被create,因此走create流程,B在初始化第一步的时候发现本身依赖了对象A,因而尝试get(A),尝试一级缓存singletonObjects(确定没有,由于A还没初始化彻底),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,因为A经过ObjectFactory将本身提早曝光了,因此B可以经过ObjectFactory.getObject拿到A对象(虽然A尚未初始化彻底,可是总比没有好呀),B拿到A对象后顺利完成了初始化阶段一、二。彻底初始化以后将本身放入到一级缓存singletonObjects中。
  • (3) 此时返回A中,A此时能拿到B的对象顺利完成本身的初始化阶段二、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,并且更加幸运的是,因为B拿到了A的对象引用,因此B如今hold住的A对象完成了初始化。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容