spring循环依赖一定要三级缓存吗?就算用来解决AOP,也需要三级缓存吗?

参考文章:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存

Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题

个人理解:
1、其实把getEarlyBeanReference生成的对象直接保存到二级缓存,无需三级缓存用ObjectFacotry封装原始bean也可以解决循环依赖。三级缓存感觉纯粹是为了延迟调用aop逻辑而已。

2、其实把getEarlyBeanReference生成的对象直接暴露到一级缓存也是可以的。只要引用的地址不变,谁要用就提前给谁。初始化动作可以后面慢慢做。只要引用不变,它初始化完成后,所有引用它的bean都自然而然的能得到完成的该bean。可能spring担心一级缓存既用来存放单例bean,又用来存放提前暴露的bean,会引起混乱。所以,上面徐庶老师说的,只要胆子大,一级缓存够用。解决循环依赖的核心,不在乎几级缓存,而在于提前暴露引用地址即可。

一、先交代下什么是循环依赖,什么是三级缓存

循环依赖:A依赖B,B依赖A
三级缓存:

//一级缓存,用来存放初始化完成的单例bean
Map<String, Object> singletonObjects;
//二级缓存,用来存放提前暴露的原始bean
Map<String, Object> earlySingletonObjects;
//三级缓存,用来存放 “包装提前暴露的原始bean”的ObjectFactory对象
Map<String, ObjectFactory<?>> singletonFactories;

有人可能会问,提前暴露的对象已经存放在二级缓存了,为啥还要在三级缓存中存放呢?下文会详细解释。

二、循环依赖的解决

    protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
        .....//省略

getBean的时候,一上来就先去拿一下提前暴露的bean对象。

getSingleton方法如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //先去一级缓存拿。新创建的bean,这里一定拿不到
        Object singletonObject = this.singletonObjects.get(beanName);
        //拿不到初始化完成的bean,且该bean正在被创建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
        //优先去二级缓存拿,如果没有再去三级缓存拿。有了,就直接返回。
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
            //最后一步,去三级缓存拿
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
        //调用三级缓存ObjectFactory的getObject得到提前暴露的对象。
                        singletonObject = singletonFactory.getObject();
        //放到二级缓存中,然后删除三级缓存。可见:同一个提前暴露的bean,只能要么在三级缓存,要么在二级缓存。
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

我们肯定会有疑问,不就去拿一个尚未初始化完成的bean对象而已嘛?有一个地方存一下,这里取出来,不就行了嘛。为啥非要在搞一个三级缓存呢?
想知道三级缓存做了啥,就要看下三级缓存的ObjectFactory.getObject到底做了啥?

三、三级缓存的ObjectFactory.getObject到底做了啥?

提前暴露对象的代码在doCreateBean里面。如下:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
            throws BeanCreationException {

        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            //实例化bean,尚未初始化
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
        Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
        mbd.resolvedTargetType = beanType;

        ..... //省略

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
      //判断是否可以提前暴露。判断条件 = 是否单例 && 是否允许(默认true) && 是否创建过程中
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
              //这里把尚未初始化的bean,包装成ObjectFactory对象传递给addSingletonFactory方法
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }

        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            //注入属性
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                //调用初始化方法,初始化bean
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }
        ...//省略

我们看下解决循环依赖的核心方法addSingletonFactory,如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
        //bean已经被其他线程初始化完成放到一级缓存了,这里也没必要放到三级缓存
            if (!this.singletonObjects.containsKey(beanName)) {
                //放到三级缓存,然后删除二级缓存(以防有值)
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

可以看到,这里仅仅是把提前暴露的bean封装成的ObjectFactory,放到三级缓存中。

是不是还是不明白为啥需要三级缓存?我们看下上面添加的匿名内部类ObjectFactory的实现。

  new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
                return getEarlyBeanReference(beanName, mbd, bean);
           }
     }

答案就在getEarlyBeanReference方法里面,如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    //对于有SmartInstantiationAwareBeanPostProcessor,特殊处理
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                    if (exposedObject == null) {
                        return null;
                    }
                }
            }
        }
        return exposedObject;
    }

这个地方专门用来特殊处理SmartInstantiationAwareBeanPostProcessor接口,说明getEarlyBeanReference也是一个拓展点,作用在这里的生命周期。getEarlyBeanReference的实现到底是啥呢?我们找个最常见的实现类,AbstractAutoProxyCreator。看下它的方法:

@Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);
        }
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

很神奇的地方,我们在看下AbstractAutoProxyCreator.postProcessAfterInitialization方法:

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

getEarlyBeanReference和postProcessAfterInitialization,何其相似啊!!!
postProcessAfterInitialization我们知道,它只会在bean的所有初始化方法完成后,调用aop生成代理类。但是getEarlyBeanReference竟然对尚未初始化完成的bean,提前进行了aop代理。Why?不用初始化完成,就能代理吗?

一直想不通,最后终于想通了。既然尚未初始化完成的bean都可以提前注入到其他bean里面,为啥就不能提前AOP呢?我们用的是bean的引用,只要这个引用不变,至于引用所指向的对象啥时候初始化完,其实无所谓。其他bean也只是持有的是这个bean的引用,同理AOP代理也是仅仅持有target bean的引用。所以,所有使用到bean的地方,只要实例化完成生成了引用地址,只要这个地址不变,就可以把这个bean当做成熟的bean使用。等整个容器启动完成,这些bean自然而然的就初始化好了,所有引用这个bean的Bean也自然而然的就可以使用了。

image.png

到这里大家应该清楚了,为啥需要三级缓存了吧。如果你依赖的对象是AOP代理,那么就需要用到第三级缓存暂存ObjectFactory。

你可能又会问,为啥这里不直接把getEarlyBeanReference生成的对象,放到二级缓存里面呢?这样不也节省了三级缓存嘛?为啥非要在getBean.getSingleton里面去调用getObject呢?

这个问题问得好,其实我也觉得可以。我目前也没答案,可能spring基于效率的考虑吧。

个人理解:
1、其实把getEarlyBeanReference生成的对象直接保存到二级缓存,无需三级缓存用ObjectFacotry封装原始bean也可以解决循环依赖。三级缓存感觉纯粹是为了延迟调用aop逻辑而已。
2、其实把getEarlyBeanReference生成的对象直接暴露到一级缓存也是可以的。只要引用的地址不变,谁要用就提前给谁。初始化动作可以后面慢慢做。只要引用不变,它初始化完成后,所有引用它的bean都自然而然的能得到完成的该bean。可能spring担心一级缓存既用来存放单例bean,又用来存放提前暴露的bean,会引起混乱。所以,上面徐庶老师说的,只要胆子大,一级缓存够用。解决循环依赖的核心,不在乎几级缓存,而在于提前暴露引用地址即可。

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