前言

在之前的内容中,咱们简要探讨了循环依靠,并指出仅通过引入二级缓存即可处理此问题。但是,你或许会好奇为安在Spring结构中还需求引入三级缓存singletonFactories。在前述总结中,我现已供给了答案,即AOP署理方针。接下来,咱们将深入探讨这一话题。

AOP

在Spring结构中,AOP的完结是通过一个名为BeanPostProcessor的类完结的,其间一个要害的BeanPostProcessor便是AnnotationAwareAspectJAutoProxyCreator。值得一提的是,该类的父类是AbstractAutoProxyCreator。在Spring的AOP机制中,通常会运用JDK动态署理或许CGLib动态署理来完结署理方针的生成。因而,假如在某个类的办法上设置了切面,那么终究这个类将需求生成一个署理方针来运用AOP的功用。

一般的履行流程通常是这样的:A类—>生成一个一般方针–>特点注入–>根据切面生成一个署理方针–>将该署理方针存入singletonObjects单例池中。

而AOP能够说是Spring结构中除了IOC之外的另一个重要功用,而循环依靠则归于IOC的范畴。因而,为了让这两个重要功用同时存在于Spring结构中,Spring需求进行特别处理。

三级缓存

在处理这种状况时,Spring结构利用了第三级缓存singletonFactories。下面咱们来看一下关于三级缓存的源代码完结:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

首要,singletonFactories中存储的是某个beanName对应的ObjectFactory。在bean的生命周期中,生成完原始方针之后,Spring结构会结构一个ObjectFactory并将其存入singletonFactories中。这个ObjectFactory是一个函数式接口,因而支撑Lambda表达式,方式为() -> getEarlyBeanReference(beanName, mbd, bean)。为了更清晰地理解这个过程,我供给一张图片。

解析Spring中的循环依靠问题:(AOP)再探三级缓存

getEarlyBeanReference

在上述Lambda表达式中,它实际上代表了一个ObjectFactory,履行该Lambda表达式将会调用getEarlyBeanReference办法。下面是getEarlyBeanReference办法的完结:

//AbstractAutowireCapableBeanFactory
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

在整个Spring结构中,值得留意的是,只有AbstractAutoProxyCreator这个类在完结getEarlyBeanReference办法时才具有真正的含义。这个类专门用于处理AOP(面向切面编程)。

// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
 Object cacheKey = getCacheKey(bean.getClass(), beanName);
 this.earlyProxyReferences.put(cacheKey, bean);
 return wrapIfNecessary(bean, beanName, cacheKey);
}

那么,getEarlyBeanReference办法的具体操作是什么呢? 首要,它会获取一个cachekey,这个cachekey实际上便是beanName。 接着,它会将beanName和bean(即原始方针)存储到earlyProxyReferences中。 接下来,它会调用wrapIfNecessary办法进行AOP操作,这将生成一个署理方针。

那么,什么时候会调用getEarlyBeanReference办法呢?让咱们再次回到循环依靠的场景中。

解析Spring中的循环依靠问题:(AOP)再探三级缓存

在我上一节的基础上,我增加了两句话,以便更好地理解触发缓存机制以处理AOP署理方针生成的机遇。

一旦原始方针通过结构办法生成后,会被存储到三级缓存中,而且会与一个lambda表达式相关。但是,在这个阶段,它并不会被履行。

一旦BBean需求ABean时,系统会首要查看三级缓存以确认是否存在缓存。假如存在缓存,则lambda表达式将会被履行,其代码已在前面展示过。该lambda表达式的意图是将署理方针放入earlySingletonObjects中。需求留意的是,此刻署理方针并未被放入singletonObjects中。那么署理方针何时会被放入singletonObjects中呢?

这个时候你或许现已明白了earlySingletonObjects的用途。由于只获取了A原始方针的署理方针,这个署理方针并不完好,因为A原始方针没有进行特点填充。因而,在这种状况下,咱们不能直接将A的署理方针放入singletonObjects中。因而,咱们只能将署理方针放入earlySingletonObjects,这样依次类推。

在Spring结构中,在循环依靠场景下,当Bean B创立完结后,Bean A持续其生命周期。在Bean A完结特点注入后,根据其本身逻辑进行AOP操作。此刻,咱们知道Bean A的原始方针现现已历了AOP处理,因而对于Bean A本身而言,不需求再次进行AOP。那么,如何确认一个方针是否现现已历了AOP呢?

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

没错,这个earlyProxyReferences确实提早缓存了方针是否现已被署理过,这样就避免了重复的AOP处理。

举例反证

那么问题来了,当A方针创立时,它能够是原始方针,但当B方针创立时,却成功创立了A的署理方针。然后再回头给A方针进行特点注入和初始化,这些操作好像与署理方针无关?

这个问题触及到了 Spring 中动态署理的完结。无论是运用cglib署理仍是jdk动态署理生成的署理类,署理时都会将方针方针 target 保存在终究生成的署理 $proxy 中。你能够将署理方针看作是对原始方针地址的一层包装,终究依然会回到原始方针上。因而,对原始bean的进一步完善实际上也便是对署理方针的完善。

还有一个需求留意的问题,当A创立时,由于earlyProxyReferences缓存的原因,并没有创立署理方针,因而此刻A依然坚持为原始方针。咱们知道,当bean创立完结后,它将被放入一级缓存中,但假如在此之后被其他方针引证,那不就会呈现问题吗?别人引证的都是原始方针了,而不是署理方针,但是请不要着急,因为在实例化之后,有一行代码能够处理这个问题。

if (earlySingletonExposure) {
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }
......省略代码

在实例化原始方针后,他会首要从三级缓存中查看是否存在缓存方针。这是因为在创立B方针时,现已将A的署理方针放入二级缓存。因而,取出的方针是署理方针。接着,当进行 exposedObject == bean 的比较时,发现它们不相同。因而,以署理方针为准并将其回来。终究,最外层存储的将是署理方针。

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

总结

在上一个章节中咱们说到了今日要评论三级缓存,让咱们根据上面说到的三级缓存内容,做一个翔实的总结:

  • singletonObjects:缓存通过了完好生命周期的bean。

  • earlySingletonObjects缓存了未通过完好生命周期的bean。当某个bean呈现循环依靠时,该bean会被提早放入earlySingletonObjects中。假如该bean需求通过AOP,那么署理方针将会被放入earlySingletonObjects;否则,原始方针将被放入其间。但是,无论是署理方针仍是原始方针,它们的生命周期都没有彻底结束。因而视为未通过完好生命周期的bean。

  • singletonFactories缓存的是一个ObjectFactory,这个ObjectFactory实际上是一个Lambda表达式。在每个Bean的生成过程中,当原始方针实例化完结后,会提早根据原始方针生成一个Lambda表达式,并将其保存到三级缓存中。这个Lambda表达式或许会被运用,也或许不会。假如当时Bean不存在循环依靠,那么这个Lambda表达式将不会被运用,当时的Bean将依照正常的生命周期履行结束,并将本身放入singletonObjects中。但是,假如在依靠注入的过程中发现了循环依靠(即当时正在创立的Bean被其他Bean所依靠),则会从三级缓存中取出Lambda表达式,并履行它以获取一个方针,然后将得到的方针放入二级缓存中。需求特别留意的是,假如当时Bean需求AOP处理,则履行Lambda表达式后得到的将是署理方针;否则,直接得到的是原始方针。

当触及Spring结构中动态署理的完结机制时,除了现已说到的earlySingletonObjects和singletonFactories这两个缓存外,还有一个重要的缓存值得一提,那便是earlyProxyReferences。这个缓存的效果在于记录某个原始方针是否现已进行过AOP(面向切面编程)处理。

至此,整个循环依靠处理结束。