一、什么是循环依赖
循环依赖就是循环引用,即两个或者多个bean相互之前的持有对方,如下图:
A引用B,B引用C,C又引用A,最终反应为一个环。
注意:此处不是循环调用,循环调用是方法之间的环调用,而且循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。
二、Spring解决循环依赖的方式
Spring容器循环依赖的场景:
- 构造器循环依赖
- setter循环依赖
Spring的循环依赖的理论依据其实是基于Java的引用传递,当获取到对象的引用时,对象的属性是可以延后设置的(但是构造器必须是在获取引用之前)。Spring的单例对象的初始化主要分为三步:
- createBeanInstance:实例化,可以说就是调用对象的构造方法实例化对象
- populateBean:属性填充,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用spring xml中的init 方法。
根据上图的单例bean初始化步骤可以知道,循环依赖主要发生在第一、第二步。即构造器循环依赖和setter循环依赖,下面我们分别对其进行说明:
1、构造器循环依赖
表示通过构造器注入构成的循环依赖,此依赖是无法解决的。
Spring容器将每一个正在创建的bean标识符放在一个 “当前创建bean池” 中,bean标识符在创建的过程中将一直保持在这个池中,因此如果在创建bean的过程中发现已经在 “当前创建bean池” 时,将抛出BeanCurrentlyCreationException异常标示循环依赖;尔对于创建完毕的bean将从 “当前创建bean池” 中清除掉。
2、setter循环依赖
表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴漏刚完成构造器注入但是未完成其他步骤(例如setter注入)的bean来完成的,并且只能解决单例作用域的bean的循环依赖。
对于 “prototype” 作用域的bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存 “prototype” 作用域的bean,因此无法提前暴漏一个创建中的bean。
通过提前暴漏一个单例工厂方法,从而使其他bean能够引用到该bean,如下代码:
AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
........
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
........
}
Spring为了解决单例的循环依赖问题,使用了三级缓存。
singletonFactories : 单例对象工厂的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonObjects:单例对象的cache
/** 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);
在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从单例缓存中家在bean
Object singletonObject = this.singletonObjects.get(beanName);
//单例缓存中bean为空且当前bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//加锁
synchronized (this.singletonObjects) {
//从earlySingletonObjects获取
singletonObject = this.earlySingletonObjects.get(beanName);
//earlySingletonObjects中没有提前创建,且允许提前创建
if (singletonObject == null && allowEarlyReference) {
//从singletonFactories中获取对应的ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//获取bean
singletonObject = singletonFactory.getObject();
//添加到earlySingletonObjects中
this.earlySingletonObjects.put(beanName, singletonObject);
//从singletonFactories中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
//添加到earlySingletonObjects中 this.earlySingletonObjects.put(beanName, singletonObject); //从singletonFactories中移除 this.singletonFactories.remove(beanName);
上面的这个操作相当于将bean从三级缓存移入到二级缓存,并删除三级缓存
从上面三级缓存的分析,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。并且cache的类型是ObjectFactory,定义如下:
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
这个在下面被调用:
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
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);
}
}
}
上面就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,即单例对象此时已经被创建出来(调用了空参构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二、三步),但是已经能根据对象引用能定位到堆中的对象,所以Spring此时将这个对象提前曝光出来让其他bean使用。
通过上面的这个原理分析,我们就可以知道Spring不能解决构造器循环依赖问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
知道了原理,我们在这里,在来说一下解决上面A、B、C三个bean的setter循环依赖的步骤:
- Spring容器创建单例 “A” bean,首先会通过A的无参构造器创造bean,并暴漏一个 “ObjectFactory” 用于返回一个提前暴漏一个创建中的bean,并将 “A” 标识符放到 “当前创建bean池” 中方,然后进行setter注入 “B”。
- Spring容器创建单例 “B” bean,首先会通过B的无参构造器创造bean,并暴漏一个 “ObjectFactory” 用于返回一个提前暴漏一个创建中的bean,并将 “B” 标识符放到 “当前创建bean池” 中方,然后进行setter注入 “C”。
- Spring容器创建单例 “C” bean,首先会通过C的无参构造器创造bean,并暴漏一个 “ObjectFactory” 用于返回一个提前暴漏一个创建中的bean,并将 “C” 标识符放到 “当前创建bean池” 中方,然后进行setter注入 “A”。进行注入 “A” 的时候由于 “A” 提前暴露了 “ObjectFactory” 工厂,从而使它返回提前暴露一个创建中的bean。
- 最后在依赖注入“B” 和 “C” ,完成setter的注入。