Spring循环引用实例分析

示例代码

@Component
public class ClassRoom {
    private String name = "天下第一班";
    @Autowired
    private Student student;
    @Override
    public String toString() {
        return "ClassRoom{" +
                "name='" + name + '\'' +
                '}';
    }
}
@Component
public class Student {
    private String name = "张三";
    @Autowired
    private ClassRoom classRoom;
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}
@ComponentScan
public class CircularReferencesTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(CircularReferencesTest.class);
        context.setAllowCircularReferences(true);
        context.refresh();

        System.out.println(context.getBean(Student.class));
        System.out.println(context.getBean(ClassRoom.class));

        context.close();
    }
}
  • ClassRoomStudent互相引用;
  • context.setAllowCircularReferences(true)在refresh前可以设置是否允许循环引用,默认是允许;

源码分析

循环引用-1.png
  • finishBeanFactoryInitialization(beanFactory)此时BeanFactory 已经创建完成,并且所有的实现了 BeanFactoryPostProcessor 接口的 Bean都已经初始化并且其中的 postProcessBeanFactory(factory) 方法已经得到回调执行了。而且 Spring 已经“手动”注册了一些特殊的 Bean,如 environment、systemProperties 等。剩下的就是初始化 singleton beans 了,如果没有设置懒加载,那么 Spring 会在这里初始化所有的 singleton beans。
  • getBean(beanName)遍历 beanDefinitionNames 通过 getBean(beanName) 去初始化 singleton beans。

下面重点看下AbstractBeanFactory.doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // 转换beanName,这里name有可能是别名也可能是FactoryBean
    final String beanName = transformedBeanName(name);
    Object bean;

    // 尝试从缓存中加载单例
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        ......
        // 返回对应实例,有时候存在BeanFactory的情况并不是直接返回实例本身而是返回指定方法返回的实例
        // 这里其实就是用于检测当前 bean 是否是 FactoryBean 类型,如果是则需要调用 FactoryBean 中的
        // getObject()作为返回值,例如 FactoryBean的使用
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        // 原型模式循环依赖直接抛错,只有在单例情况下才会尝试解决循环依赖,即 A 中有 B 的属性,B 中有 A的属性,
        // 依赖注入时,会产生 A 未创建完对于 B 的创建再次返回创建 A,的情况
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        ......
        // 实例化依赖 bean 之后,开始实例化 bean 本身
        if (mbd.isSingleton()) {
            // 缓存中不存在,开始直接去加载单例 Bean
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    // getSingleton通过回调该方法,真正去创建 Bean
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
        ......
    }
    ......
    return (T) bean;
}
  • Object sharedInstance = getSingleton(beanName)尝试从缓存中加载 bean,稍后详细分析;
  • bean = getObjectForBeanInstance(sharedInstance, name, beanName, null)返回对应实例,有时候存在 BeanFactory 的情况并不是直接返回实例本身,这里其实就是用于检测当前 bean 是否是 FactoryBean 类型,如果是则需要调用 FactoryBean 中的 getObject() 作为返回值,例如 FactoryBean的使用;
  • isPrototypeCurrentlyInCreation(beanName)当前 beanName 的原型模式的 bean 正在创建,说明原型模式存在循环依赖,直接抛出异常,只有在单例情况下才会尝试解决循环依赖;
  • sharedInstance = getSingleton缓存中不存在,开始直接去加载单例 Bean;

小结一下就是:首先从缓存中获取 bean 实例,存在的话返回;否则通过getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法并注册回调函数createBean(beanName, mbd, args)去创建 bean 实例,其中包含缓存的维护以及循环引用的处理逻辑;

再来重点看下getSingleton(beanName)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    // 缓存中不存在,并且正在创建的单例
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 当某些方法需要提前初始化的时候会调用 addSingletonFactory 将对应的
                // ObjectFactory 初始化策略存储在 singletonFactories
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 调用预先设定的 getObject 方法
                    singletonObject = singletonFactory.getObject();
                    // 存储在缓存中,earlySingletonObjects 和 singletonFactories互斥
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

这里涉及3个Map1个Set

private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));   
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  1. singletonsCurrentlyInCreation存放当前正在创建的单例 bean,bean 创建完成后移除,用来判断是否正在创建;
  2. singletonObjects存放直接可用的成品 bean;
  3. earlySingletonObjects存放半成品的 bean(已经调用构造,但属性尚未赋值),提前曝光的 bean,用于解决循环依赖;
  4. singletonFactories半成品工厂,存放 ObjectFactory<?> 类型对象,用来生成半成品 bean 放入 earlySingletonObjects;

小结一下这里的逻辑(allowEarlyReference默认为true,这里忽略):

  1. singletonObjects中获取到成品 bean 则直接返回,否则goto 2;
  2. 通过 singletonsCurrentlyInCreation判断当前 bean 是否正在创建,不是则返回 null 到 doGetBean 中进行创建,是则goto 3;
  3. earlySingletonObjects中获取提前曝光的半成品 bean,存在则直接返回,否则goto 4;
  4. singletonFactories中获取单例 bean 工厂,用来生成半成品 bean,不存在则返回null,否则goto 5;
  5. 通过预先设定的 getObject 方法生成半成品 bean,并存储放入earlySingletonObjects,由于单例bean 已经生成,则对应的 SingletonObject从singletonFactories中移除;

再回到doGetBean看下缓存中不存在需要去创建的逻辑

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        // 再次检查是否已经加载过
        if (singletonObject == null) {
            ......
            // 加载单例前记录加载状态,beanName 加到 singletonsCurrentlyInCreation 中
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            ......
            try {
                // 通过回调方法 singletonFactory 实例化 Bean
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            ......
            finally {
                // 加载完成 beanName 从singletonsCurrentlyInCreation 中移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 添加到 singletonObjects / registeredSingletons,
                // 从 singletonFactories / earlySingletonObjects 中删除
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}
  1. beforeSingletonCreation(beanName)创建 bean 之前存入singletonsCurrentlyInCreation中,到这里如果已经存在当前 bean 则会抛出循环引用的异常,对应context.setAllowCircularReferences(false)的场景;
Requested bean is currently in creation: Is there an unresolvable circular reference?
  1. singletonFactory.getObject()通过 doGetBean 中注册的回调函数创建 bean,下面详细分析;
  2. afterSingletonCreation(beanName)维护singletonsCurrentlyInCreation,即创建后从里面移除;
  3. addSingleton(beanName, singletonObject)添加到 singletonObjects,并从singletonFactories / earlySingletonObjects中删除,及bean 创建完成后加入成品Map,同时从半成品Map和半成品工厂中移除当前 bean;

小结一下:这里就是真正去创建 bean,并且维护3个Map1个Set;

下面再来分析回调函数中直接创建 bean的逻辑createBean(beanName, mbd, args)

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    ......
    if (instanceWrapper == null) {
        // 创建 Bean 实例,将 BeanDefinition 转换成 BeanWrapper
        // 1. 如果存在工厂方法则使用工厂方法进行初始化
        // 2. 一个类有多个构造方法,每个构造方法都有不同的参数,所以需要根据参数锁定构造函数并进行初始化
        // 3. 如果既不存在工厂方法也不存在带有参数的构造函数,使用默认的构造函数进行实例化
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }
    ......
    /**
     * 重要:::A 依赖 B,B 又依赖 A,如何解决循环依赖
     * 在 B 中创建依赖 A 时通过 ObjectFactory 提供的实例化方法来中断 A 中的属性填充,使 B 中持有的 A 仅仅是刚刚初始化并没有填充任何属性的 A,
     * 而正在初始化 A 的步骤还是在最开始创建 A 的时候进行的,但是因为A与B中的A所代表的属性地址是一样的,所以A中创建好的属性填充自然可以通过
     * B 中的A获取,这样就解决了循环依赖的问题
     */

    // 是否需要提早曝光:单例 & 允许循环依赖 & 当前 bean 正在创建中,检测循环依赖  allowCircularReferences默认 true
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName)); // DefaultSingletonBeanRegistry 类的 getSingleton 函数的 beforeSingleton 和 afterSingleton中来进行状态的记录与移除
    if (earlySingletonExposure) {
        // 为避免后期循环依赖可以在 bean 初始化完成前将创建实例的 ObjectFactory 加入 singletonFactories registeredSingletons
        addSingletonFactory(beanName, () -> {
            // 对 bean 再一次依赖引用,主要应用 SmartInstantiationAware BeanPostProcessor
            // AOP 就是在这里将 advice 动态织入 bean 中,若没有则直接返回bean,不做任何处理
            return getEarlyBeanReference(beanName, mbd, bean);
        });
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 这一步也是非常关键的,这一步负责属性装配,因为前面的实例只是实例化了,并没有设值,这里就是设值
        // 对 bean 进行填充,将各个属性值注入,其中可能存在依赖于其他bean 的属性,进行递归初始化
        populateBean(beanName, mbd, instanceWrapper);
        // 还记得 init-method 吗?还有 InitializingBean 接口?还有 BeanPostProcessor 接口?
        // 这里就是处理 bean 初始化完成后的各种回调
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    ......
    return exposedObject;
}

我们这里主要分析循环引用,忽略其他逻辑

  1. createBeanInstance(beanName, mbd, args)通过 BeanDefinition 获取构造函数去真正创建 bean 实例,并包装成 BeanWrapper;
  2. earlySingletonExposure是否需要提前曝光,即是否需要把这个 bean 应用半成品的逻辑;单例 & 允许循环依赖 & 当前 bean 正在创建中;
  3. addSingletonFactory维护半成品工厂和半成品Map,半成品工厂获取 bean 通过回调函数getEarlyBeanReference生成,这里默认返回上面创建的 bean,也可以通过实现SmartInstantiationAwareBeanPostProcessor接口进行维护;
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
  1. populateBean对创建的 bean 进行初始化,对 bean 进行填充,将各个属性值注入,其中可能存在依赖于其他bean 的属性(例如@Autowired),最终通过调用getBean方法进行递归创建;

小结一下:这里首先去创建 bean,然后判断是否需要经过半成品 -> 成品的流程,需要的话构造一个ObjectFactory放入singletonFactories,供后面通过缓存获取 bean 时使用(doGetBean中getSingleton(beanName))。

实例分析

循环引用-2.png

可以运行上面示例代码,在下面几个地方加上条件断点(beanName.equals("classRoom") || beanName.equals("student"))进行debug

AbstractBeanFactory#doGetBean - Object sharedInstance = getSingleton(beanName);
AbstractBeanFactory#doGetBean - sharedInstance = getSingleton(beanName, () -> {...});
AbstractAutowireCapableBeanFactory#doCreateBean - if (earlySingletonExposure)
DefaultSingletonBeanRegistry#getSingleton - afterSingletonCreation(beanName);
DefaultSingletonBeanRegistry#getSingleton - addSingleton(beanName, singletonObject);

总结

  • Spring 默认支持单例 bean 的循环引用,也可以通过设置context.setAllowCircularReferences(true)关闭循环引用;
  • Spring 通过3个Map1个Set来解决循环引用的问题,大致思路就如下:

1.在加载 bean 之前先放到singletonsCurrentlyInCreation中表示该bean已经正在创建;
2.如果允许循环引用则先构造单例泛型bean工厂 ObjectFactory<?>放入singletonFactories
3.然后在具体碰到循环引用时取出刚才的ObjectFactory<?>创建循环引用的实例,然后暂存在earlySingletonObjcets表示半成品bean,因为此时尚未进行初始化其依赖的属性尚未赋值;
4.解决了这个循环引用的问题,这时内部被依赖的 bean就可以顺利初始化完成并加入到singletonObjcets这个成品bean集合中(后续getBean都是直接从这里面获取);
5.内部依赖bean构建完成继续回去完成外层bean的初始化(这时才对依赖bean的属性进行赋值),然后也放到singletonObjcets中;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容