解决 bean 之间的循环依赖分为2种:
- 构造函数注入导致的循环依赖
发现这种情况, spring无解, 直接抛出BeanCurrentlyInCreationException
异常 - setter注入导致的循环依赖
spring 只解决 singleton bean 下的 setter 注入循环依赖, 其它 scope 下依然会抛出BeanCurrentlyInCreationException
异常. spring 将正在创建的 singleton bean 提前暴露出来, 供其它 bean 提前引用
1. 为什么 spring 只解决 singleton scope 下的环形依赖
prototype scope 的 bean 是一个 ThreadLocal 变量. 在 AbstractBeanFactory 的 #doGetBean
方法中, 调用了 afterPrototypeCreation(beanName)
prototypesCurrentlyInCreation 是一个 ThreadLocal 集合, bean 在被创建之前加入这个集合, 创建后从这个集合中删除.(如下代码体现了这个过程)
// AbstractBeanFactory.java
private final ThreadLocal
在 AbstractBeanFactory
类的 #doGetBean()
方法中, 首先判断是否是一个 singleton 的 bean, 如果不是, 判断该 bean 是否正在当前线程中创建, 如果是, 抛出异常
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set>) curVal).contains(beanName))));
}
2. 为什么只能解决构造器出入的环形依赖
问题的答案同 prototype , 代码在 DefaultSingletonBeanRegistry
类的 #beforeSingletonCreation()
方法 . 创建 bean 之前, 会去 inCreationCheckExclusions
集合判断是否正在创建, 是的话抛出异常
// DefaultSingletonBeanRegistry.java
private final Set inCreationCheckExclusions =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
3. spring 如何解决 singleton scope 下的 setter 环形依赖
之所以能够解决 setter 环形依赖, 是因为构造器构造对象后, 堆内存中已经分配了内存, 即使再设置属性, 也不会改变对象的地址引用. 为了在创建时获取 singleton 的 bean , spring 使用了三级缓存结构
/** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);
三级缓存如下:
- L1 cache:
singletonObjects
存储已经实例化好的 bean - L2 cache:
earlySingletonObjects
提前暴露的 singleton 缓存, 仅通过构造器反射, 还没有注入属性的 bean - L3 cache:
singletonFactories
对创建 singleton bean 的工厂缓存
创建 bean 的第一步是创建一个同名的 single factory, 这个 factory 也是 bean,
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
......
// () -> getEarlyBeanReference 表示一个匿名的ObjectFactory类, 其 getBean 方法调用了 getEarlyBeanReference() 方法
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
.....
}
// 表示调用 ObjectFactory 工厂的 getBean 时, 会执行 getEarlyBeanReference 方法
// 该方法会处理一个 SmartInstantiationAwareBeanPostProcessor 类型的 BeanPostProcessor, 获取 earlyReference 的代理对象
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;
}
addSingletonFactory()
方法如下. 可以发现, 创建 bean 的 single factory 被放到三级缓存
// DefaultSingletonBeanRegistry.java
protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
Map var3 = this.singletonObjects;
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
从缓存中获取 bean 的方法在 getSingleton()
, 在获取 singleton bean 作为属性注入时,
(1) 首先从 L1 cache 中获取 singleton bean
(2) 如果找不到, 可能是正在创建, 所以去 L2 cache 中查找
(3) 如果找不到, 就去 L3 级的 cache 中找到创建 bean 的工厂, 调用工厂的 #getBean()
获取 singleton bean, 再将创建出的 bean 放到二级缓存中. 这样, 就把 bean 的缓存从三级缓存提升到二级缓存中;
// DefaultSingletonBeanRegistry.java
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;
}
问题
( 1 ) 生么时候, bean 从二级缓存提升到一级缓存中?
在 bean 完成属性注入等一些列操作后, bean 已经构建完毕, 将加入一级缓存, 并从二三级缓存中删除, 相当于缓存提升
( 2 ) 为什么要有第一级缓存singletonObjects
1.先说一级缓存 singletonObjects
。实际上,一级依赖已经可以解决循环依赖的问题,假设两个beanA和beanB相互依赖,beanA被实例化后,放入一级缓存,即使没有进行初始化,但是beanA的引用已经创建(栈到堆的引用已经确定),其他依赖beanB已经可以持有beanA的引用,但是这个bean在没有初始化完成前,其内存(堆)里的字段、方法等还不能正常使用,but,这并不影响对象之间引用持有;这个时候beanA依赖的beanB实例化,beanB可以顺利拿到beanA的引用,完成beanB的实例化与初始化,并放入一级缓存,在beanB完成创建后,beanA通过缓存顺利拿到beanB的引用,至此,循环依赖只需一层缓存就能完成。
2.一级缓存的关键点在与:bean实例化与初始化的分离。从JVM的角度,实例化后,对象已经存在,其内的属性都是初始默认值,只有在初始化后才会赋值,以及持有其他对象的引用。通过这个特性,在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。
( 3 ) 说说第三级缓存singletonFactories
上述我们通过一级缓存已经拿到的对象有什么问题?根本问题就是,我们拿到的是bean的原始引用,如果我们需要的是bean的代理对象怎么办?Spring里充斥了大量的动态代理模式的架构,典型的AOP就是动态代理模式实现的,再比如我们经常使用的配置类注解@Configuration在缺省情况下(full mode
),其内的所有@Bean都是处于动态代理模式,除非手动指定proxyBeanMethods = false将配置转成简略模式(lite mode)。所以,Spring在bean实例化后,将原始bean放入第三级缓存singletonFactories中,第三级缓存里实际存入的是ObjectFactory接口签名的回调实现。通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。
( 4 ) 为什么需要earlySingletonObjects这个二级缓存?并且,如果只有一个缓存的情况下,为什么不直接使用singletonFactories这个缓存,即可实现代理又可以缓存数据。
从软件设计角度考虑,三个缓存代表三种不同的职责,根据单一职责原理,从设计角度就需分离三种职责的缓存,所以形成三级缓存的状态。再次说说三级缓存的划分及其作用。
一级缓存singletonObjects
是完整的bean,它可以被外界任意使用,并且不会有歧义。
二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。
在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。