我们都知道Spring通过三级缓存来解决循环依赖的问题,那么是不是必须是三级缓存?二级缓存不能解决吗?先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成,这个都不陌生。Spring的循环依赖有2种场景
- 构造器的循环依赖(singleton,prototype)
- 属性的循环依赖(singleton,prototype)
spring目前只能解决singleton类型的属性循环依赖,而构造函数的循环依赖Spring无法解决。构造器的循环依赖,可以在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入
@Autowired
public ConstructorB(@Lazy ConstructorA constructorA) {
this.constructorA = constructorA;
}
但这种方式不被推荐,在开始后面的内容的时候,我们先明确2个概念
- 实例化:调用构造函数将对象创建出来,
半成品Bean
- 初始化:调用构造函数将对象创建出来后,对象的属性也被赋值,
成品Bean
Spring解决循环依赖的核心思想在于提前曝光,在Spring的源码类 DefaultSingletonBeanRegistry 中定义了三级缓存
/** 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 |
singletonFactories | 三级缓存,存放Bean工厂对象 ,用来生成半成品Bean |
Spring从缓存中取对象的源码
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;
}
从三个缓存的作用来看,在A依赖B,B依赖A的场景:先生成A的半成品放到二级缓存,然后去生成B,这时候B可以在二级缓存找到A,然后B初始化完成,把成品B放到一级缓存,这时再去检查一下二级缓存,发现A还没有初始化,但可以在一级缓存中找到它依赖的B,于是A也可以初始化完成,似乎两级缓存也能解决循环依赖的问题,那为什么要包装一个ObjectFactory对象?
如果创建的Bean有对应的aop代理,那其他对象注入时,注入的应该是对应的代理对象,「但是Spring无法提前知道这个对象是不是有循环依赖的情况」,而正常情况下(没有循环依赖情况),Spring都是在对象初始化后才创建对应的代理。这时候Spring有两个选择:
- 不管有没有循环依赖,实例化后就直接创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入(只需要2级缓存,singletonObjects和earlySingletonObjects即可)
- 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才提前生成代理对象(此时只完成了实例化)。这样在没有循环依赖的情况下,Bean还是在初始化完成才生成代理对象(需要3级缓存)
Spring选择了第二种方式「所以到现在为止你知道3级缓存的作用了吧,主要是为了正常情况下,代理对象能在初始化完成后生成,而不用提前生成」如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。提前生成代理对象违背了Spring的设计初衷
前面说到Sping选择了第二种,如果是第一种,就会有以下不同的处理逻辑:
- 在提前曝光半成品时,直接执行getEarlyBeanReference创建到代理,并放入到缓存earlySingletonObjects中。
- 有了上一步,那就不需要通过ObjectFactory来延迟执行getEarlyBeanReference,也就不需要singletonFactories这一级缓存。
这种处理方式可行吗?
这里做个试验,对AbstractAutowireCapableBeanFactory做个小改造,在放入三级缓存之后立刻取出并放入二级缓存,这样三级缓存的作用就完全被忽略掉,就相当于只有二级缓存。
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启动速度。
总结
只使用二级缓存可以解决循环依赖的问题,Spring为什么用三级缓存?从设计角度出发,只使用二级缓存需要提前生成代理对象,违背了设计原则。所以使用了三级缓存