Spring循环依赖三级缓存是否能够减小为二级缓存?

基于Spring-5.1.5.RELEASE

问题

都知道Spring经过三级缓存来解决循环依赖的问题。可是是否是必须三级缓存才能解决,二级缓存不能解决吗?
要分析是否是能够去掉其中一级缓存,就先过一遍Spring是如何经过三级缓存来解决循环依赖的。java

循环依赖

所谓的循环依赖,就是两个或则两个以上的bean互相依赖对方,最终造成闭环。好比“A对象依赖B对象,而B对象也依赖A对象”,或者“A对象依赖B对象,B对象依赖C对象,C对象依赖A对象”;相似如下代码:面试

public class A {
    private B b;
}

public class B {
    private A a;
}

常规状况下,会出现如下状况:缓存

  1. 经过构建函数建立A对象(A对象是半成品,还没注入属性和调用init方法)。
  2. A对象须要注入B对象,发现对象池(缓存)里尚未B对象(对象在建立而且注入属性和初始化完成以后,会放入对象缓存里)。
  3. 经过构建函数建立B对象(B对象是半成品,还没注入属性和调用init方法)。
  4. B对象须要注入A对象,发现对象池里尚未A对象。
  5. 建立A对象,循环以上步骤。

三级缓存

Spring解决循环依赖的核心思想在于提早曝光app

  1. 经过构建函数建立A对象(A对象是半成品,还没注入属性和调用init方法)。
  2. A对象须要注入B对象,发现缓存里尚未B对象,将半成品对象A放入半成品缓存
  3. 经过构建函数建立B对象(B对象是半成品,还没注入属性和调用init方法)。
  4. B对象须要注入A对象,从半成品缓存里取到半成品对象A
  5. B对象继续注入其余属性和初始化,以后将完成品B对象放入完成品缓存
  6. A对象继续注入属性,从完成品缓存中取到完成品B对象并注入。
  7. A对象继续注入其余属性和初始化,以后将完成品A对象放入完成品缓存

其中缓存有三级:ide

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);


/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
缓存 说明
singletonObjects 第一级缓存,存放可用的成品Bean
earlySingletonObjects 第二级缓存,存放半成品的Bean半成品的Bean是已建立对象,可是未注入属性和初始化。用以解决循环依赖。
singletonFactories 第三级缓存,存的是Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。用以解决循环依赖。

要了解原理,最好的方法就是阅读源码,从建立Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手。函数

1. 在构造Bean对象以后,将对象提早曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性初始化

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {            
        ……
        // 是否提早曝光
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        ……
    }   
}

2. 提早曝光的对象被放入Map<String, ObjectFactory<?>> singletonFactories缓存中,这里并非直接将Bean放入缓存,而是包装成ObjectFactory对象再放入。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    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);
            }
        }
    }
}
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

3. 为何要包装一层ObjectFactory对象?

若是建立的Bean有对应的代理,那其余对象注入时,注入的应该是对应的代理对象;可是Spring没法提早知道这个对象是否是有循环依赖的状况,而正常状况下(没有循环依赖状况),Spring都是在建立好完成品Bean以后才建立对应的代理。这时候Spring有两个选择:post

  1. 无论有没有循环依赖,都提早建立好代理对象,并将代理对象放入缓存,出现循环依赖时,其余对象直接就能够取到代理对象并注入。
  2. 不提早建立好代理对象,在出现循环依赖被其余对象注入时,才实时生成代理对象。这样在没有循环依赖的状况下,Bean就能够按着Spring设计原则的步骤来建立。

Spring选择了第二种方式,那怎么作到提早曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提早曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));性能

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }
}

为了防止对象在后面的初始化(init)时重复代理,在建立代理时,earlyProxyReferences缓存会记录已代理的对象。测试

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
            
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }        
}

4. 注入属性和初始化

提早曝光以后:this

  1. 经过populateBean方法注入属性,在注入其余Bean对象时,会先去缓存里取,若是缓存没有,就建立该对象并注入。
  2. 经过initializeBean方法初始化对象,包含建立代理。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
        ……
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            }
            else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
            }
        }
        ……
    }        
}    
// 获取要注入的对象
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    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) {
                    // 三级缓存
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
}

5. 放入已完成建立的单例缓存

在经历了如下步骤以后,最终经过addSingleton方法将最终生成的可用的Bean放入到单例缓存里。

  1. AbstractBeanFactory.doGetBean ->
  2. DefaultSingletonBeanRegistry.getSingleton ->
  3. AbstractAutowireCapableBeanFactory.createBean ->
  4. AbstractAutowireCapableBeanFactory.doCreateBean ->
  5. DefaultSingletonBeanRegistry.addSingleton
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    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);
        }
    }
}

二级缓存

上面第三步《为何要包装一层ObjectFactory对象?》里讲到有两种选择:

  1. 无论有没有循环依赖,都提早建立好代理对象,并将代理对象放入缓存,出现循环依赖时,其余对象直接就能够取到代理对象并注入。
  2. 不提早建立好代理对象,在出现循环依赖被其余对象注入时,才实时生成代理对象。这样在没有循环依赖的状况下,Bean就能够按着Spring设计原则的步骤来建立。

Sping选择了第二种,若是是第一种,就会有如下不一样的处理逻辑:

  1. 提早曝光半成品时,直接执行getEarlyBeanReference建立到代理,并放入到缓存earlySingletonObjects中。
  2. 有了上一步,那就不须要经过ObjectFactory延迟执行getEarlyBeanReference,也就不须要singletonFactories这一级缓存。

这种处理方式可行吗?
这里作个试验,对AbstractAutowireCapableBeanFactory作个小改造,在放入三级缓存以后马上取出并放入二级缓存,这样三级缓存的做用就彻底被忽略掉,就至关于只有二级缓存
image

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {            
        ……
        // 是否提早曝光
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
            // 马上从三级缓存取出放入二级缓存
            getSingleton(beanName, true);
        }
        ……
    }   
}

测试结果是能够的,而且从源码上分析能够得出两种方式性能是同样的,并不会影响到Sping启动速度。那为何Sping不选择二级缓存方式,而是要额外加一层缓存?
若是要使用二级缓存解决循环依赖,意味着Bean在构造完后就建立代理对象,这样违背了Spring设计原则。Spring结合AOP跟Bean的生命周期,是在Bean建立彻底以后经过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。若是出现了循环依赖,那没有办法,只有给Bean先建立代理,可是没有出现循环依赖的状况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

参考:

面试官:聊聊Spring源码的生命周期、循环依赖
面试必杀技,讲一讲Spring中的循环依赖
相关文章
相关标签/搜索