Spring如何解决循环依赖
想彻底弄清楚spring的循环依赖问题,首先得弄清除
-
循环依赖是如何发生的
,Spring又是如何检测循环依赖的发生的。 - 其次再探究Spring
如何解决循环
的依赖的问题
最后我们总结循环依赖解决的2个关键因素,提前曝光
和曝光时机
,缺一不可
循环依赖检查
<bean id="a" class="A">
<property name="b" ref="b">
<bean/>
<bean id="b" class="B">
<property name="a" ref="a">
<bean/>
无论单例还是原型模式(下文①代表图中步骤1),Spring都有对应的集合保存当前正在创建的beanName
,标识该beanName正在被创建。①在bean创建前, 会检查当前bean是否在创建中
,如果不在创建中则②将beanName中加入集合
,往下创建bean。在bean创建前,检测到当前的bean正在创建
,则说明发生循环依赖
。抛出异常。最后记得当bean创建完时将beanName移除集合
。
循环依赖的处理
单例setter循环依赖
Spring注入属性的方式有很多种,但是只有一种循环依赖能被解决:setter依赖注入
.处理方法
就是 未等bean创建
完成就先将实例曝光出去,方便其他bean的引用。同时还提到了三级缓存,最先曝光到三级缓存singletonFactories
中。简单来说,就是Spring先创建好的实例放在缓存中,让其他bean可以提前引用该对象。
示例
// 第一种 注解方式
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
// ===========================
// 第二种 xml配置方式
public class A {
private B b;
// getter setter
}
public class B {
private A a;
// getter setter
}
<bean id="a" class="A">
<property name="b" ref="b">
<bean/>
<bean id="b" class="B">
<property name="a" ref="a">
<bean/>
分析
- 提前曝光,如果用c语言的说法就是将指针曝光出去,用
java
就是将引用对象
曝光出去。也就是说即使a对象还未创建完成,但是在④实例化过程中new A()
动作开辟了一块内存空间
,只需要将该地址抛出去b就可以引用的到,而不管a后期还会进行初始化其他操作。 - 已经了解了
提前曝光
的作用,而相比而言⑤曝光的时机
也非常的重要,该时机发生在④实例化
之后,填充与初始化
之前。Spring循环依赖之所以不能解决实例化注入的原因正是因为注入时机在曝光之前所导致。 - ⑤中写的带a的工厂是什么东西?先来了解一下ObjectFatory
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
就是一个接口,通过重写getObject()方法返回对应的object
// 将该bean提前曝光,具体做法是创建一个ObjectFactory对象,再将对象加入到singletonFactories缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
getEarlyBeanReference(beanName, mbd, bean);
}
});
但是我们看到,按原计划重写getObject()
应该直接return bean 就行了,为什么还有getEarlyBeanReference
是什么鬼?(这点非常重要,但是我看了很多博客甚至书本都完全忽视了这点,如果忽视了这点,那三级缓存将失去意义,直接二级缓存就可以解决提前曝光的问题)
getEarlyBeanReference
目的就是为了后置处理,给一个在提前曝光时操作bean的机会,具体怎么操作,那就继承SmartInstantiationAwareBeanPostProcessor
重写getEarlyBeanReference
方法吧。比如你要System.out.print(“啊啊啊啊,我是” + bean + “,我被曝光且提前引用啦”)也是可以的,关键就在于bean被曝光到三级缓存
时并没有使用提前曝光
的后置处理,而是当三级缓存被提前引用到二级缓存才触发!(但是在Spring的源码中,真正实现这个方法的只有AbstractAutoProxyCreator
这个类,用于提前曝光的AOP代理。)
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;
// 这么一大段就这句话是核心,也就是当bean要进行提前曝光时,
// 给一个机会,通过重写后置处理器的getEarlyBeanReference方法,来自定义操作bean
// 值得注意的是,如果提前曝光了,但是没有被提前引用,则该后置处理器并不生效!!!
// 这也正式三级缓存存在的意义,否则二级缓存就可以解决循环依赖的问题
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
单例构造器注入循环依赖
上面已经剧透了这个方式不得行,原因依赖注入
的时间点不对,他的依赖注入发生在构造器阶段,这个时候连实例都没有,内存都还没有开辟完。当然也还没有进行提前曝光,因此不得行。
示例
public class A {
private B b;
@Autowired
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
@Autowired
public B(A a) {
this.a = a
}
}
分析
图上重点地方也用黄色标出了,问题的原因处在④实例化,实例化的过程是调用new A(B b);
的过程,这时的A还未创建
出来,根本是不可能提前曝光
的,正是这个原因导致⑨无法获取到三级缓存,进而导致⑩异常的抛出
原型模式循环依赖
这此没有图了,因为原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测
,发现原型模式下存在循环依赖并抛出异常
总结
总结一下循环依赖,spring只能解决setter注入单例模式下的循环依赖问题。要想解决循环依赖必须要满足2个条件:
- 需要用于
提前曝光
的缓存 - 属性的
注入时机
必须发生在提前曝光
动作之后,不管是填充
还是初始化
都行,总之不能在实例化
,因为提前曝光动作在实例化之后
理解了这2点就可以轻松驾驭循环依赖了。比如构造器注入是不满足第二个条件,曝光时间不对。而原型模式则是缺少了第一个条件,没有提前曝光的缓存供使用
Spring从缓存中获取bean
以下是Spring获取Bean的核心源码:
//真正实现向IOC容器获取Bean的功能,也是触发依赖注入功能的地方
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//根据指定的名称获取被管理Bean的名称,剥离指定名称中对容器的相关依赖
//如果指定的是别名,将别名转换为规范的Bean名称
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//先从缓存中取是否已经有被创建过的单态类型的Bean
//对于单例模式的Bean整个IOC容器中只创建一次,不需要重复创建
Object sharedInstance = getSingleton(beanName);
//IOC容器创建单例模式Bean实例对象
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
//如果指定名称的Bean在容器中已有单例模式的Bean被创建
//直接返回已经创建的Bean
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
//获取给定Bean的实例对象,主要是完成FactoryBean的相关处理
//注意:BeanFactory是管理容器中Bean的工厂,而FactoryBean是创建创建对象的工厂Bean,两者之间有区别
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
// 这节我们只关心第二点,因此后面先暂时注释掉
...
}
转换为真实的beanName
由于传入的beanName
可能带&前缀
需要获取工厂,也有可能是别名
。因此需要做以下转换,找出真实的beanName
protected String transformedBeanName(String name) {
// 先调用transformedBeanName去除前缀,再调用canonicalName替换别名
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
// 去除前缀
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
// FACTORY_BEAN_PREFIX = "&",不包含&前缀直接返回
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
return name;
}
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
// 去除FACTORY_BEAN_PREFIX前缀,知道不包含前缀
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}
// 替换别名
public String canonicalName(String name) {
String canonicalName = name;
// Handle aliasing...
String resolvedName;
do {
// 通过aliasMap获取beanName,直到不再有别名
resolvedName = this.aliasMap.get(canonicalName);
// 可能存在A->B->C->D,所以需要一直循环直到没有别名位置
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}
从缓存中获取bean
// AbstractBeanFactory.java
// 2. 尝试从缓存中获取bean
Object sharedInstance = getSingleton(beanName);
这里我将缓存分为三级:
-
singletonObject:
一级缓存,该缓存key =beanName,value=bean;
这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化
以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存。
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
earlySingletonObjects:
二级缓存,该缓存key=beanName,value=bean;
这里跟一级缓存的区别在于,该缓存锁获取到的bean是提前曝光出来的,是还没有创建完成的**。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化还没有做完(AOP情况后续分析),因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用**
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
-
registeredSingletons
:保存着锁注册过单例的beanName
,是一个set确保不重复
/** 已注册的单例集,按注册顺序包含Bean名称 */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
我们将看一下从缓存中获取Bean的代码
// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存获取,key=beanName value=bean
Object singletonObject = this.singletonObjects.get(beanName);
// singletonObject为空,且该bean正在创建中(假设不在创建中那么肯定是还没被实例化以及提前曝光的,继续查找没有意义)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从二级缓存获取,key=beanName value=bean
singletonObject = this.earlySingletonObjects.get(beanName);
// 是否允许循环引用
if (singletonObject == null && allowEarlyReference) {
// 从缓存中获取BeanFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过getObject()方法获取bean,获取到的实例不单单是提前曝光出来的实例,它还经过了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法处理过。这也正是三级缓存存在的意义,用户可以通过重写该后置处理器对提前曝光的实例进行一些操作
singletonObject = singletonFactory.getObject();
// 将三级缓存生产的bean放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 删除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}