什么是循环依赖?
循环依赖就是循环引用,就是两个或者多个bean相互之间的持有对方,
比如A引用B,B引用C,而C又引用A,这样它们就最终反映为一个环。
注意这里不是循环调用,循环调用是方法之间的环调用,
循环调用是无法解决的,除非有终结条件,否则就是死循环,最终会导致内存溢出错误。
spring如何解决依赖循环
先说分类,spring的循环依赖分为:构造器循环依赖和setter循环依赖。
结论是:构造器循环依赖没有办法解决,spring会直接抛错。
setter中的bean循环依赖解决必须使单例模式的,否则也是无法解决的
下面详细说一下各种的特点和具体表现
1.构造器循环依赖
表示通过构造器注入构成的循环依赖,无法解决,抛出
BeanCurrentlyInCreationException异常来表示发生了循环依赖。
举个例子:
假设通过要创建A类的时候,构造器需要B类,这是就需要去创建B类,但是在创建B的时候发现B的构造器需要C类,
这时就需要去创建C,但是不巧的是C的构造器也需要A类,那么就形成了一个环,没有办法创建。
Spring有个机制是,会将每一个正在创建的bean标识符放在“当前创建bean池”中,创建过程中会一直保持,创建完毕就会将bean标识符从池子中清除掉。
如果创建过程中发现自己已经在该池中时就会抛出
BeanCurrentlyInCreationException异常表示依赖循环。
2.setter循环依赖
定义为通过setter注入方式构成的循环依赖。
解决的原理是:
通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来解决setter循环依赖。
而且只能解决单例作用域的bean依赖循环。
通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bena
源代码如下:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
/**
*添加给定的单例工厂来构建指定的单例
*如果有必要。
/ 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); } } } /*
- 获取用于早期访问指定bean的引用,
- 通常用于解析循环引用。
*/
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;
}
我们举一个列子:
默认是“singleton”作用域。
需求还是创建A,B,C三个类
1.Spring容器创建单例“A”bean,首先会根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露创建中的bean,并将“A”标识符放到“当前创建bean池”,然后进行setter方式注入“B”。
2.Spring容器创建单例“B”bean,首先会根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露创建中的bean,并将“B”标识符放到“当前创建bean池”,然后进行setter方式注入“C”。
3.Spring容器创建单例“C”bean,首先会根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露创建中的bean,并将“C”标识符放到“当前创建bean池”,然后进行setter方式注入“C”。
这里在注入A的时候由于提前暴露了“ObjectFactory”工厂,从而可以使用工厂返回提前暴露一个创建中的bean,完成“C”的注入。
4.最后依赖注入“B”和“A”,完成setter注入。
3.prototype范围的依赖处理
结论:对于“prototype”作用域的bean,Spring容器无法完成依赖注入,
因为Spring容器规定不会进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean。
配置示例:
注意: 问题: 那么如果可以控制bean之前能否循环引用吗?
回答:可以的。
对“singleton”作用域的bean,可以通过“
setAllowCircularReferences(false);”来禁用循环引用。
该属性表示是否允许bean之间的循环引用并自动尝试解析它们,默认是true。
设置为false时可在遇到循环引用时引发异常,完全不允许循环引用。
FactoryBean
FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。
一般情况下,Spring是通过反射机制利用bean的class属性指定实现类来实例化bean的。
但是在某些特殊情况下,实例化bean过程比较复杂,属性要求很多,这时如果再继续采用传统的xml配置方式,
就需要在中提供大量的配置信息了,灵活性受到了限制。
在这样的背景下,就产生了采用编码方式提供一个
org.springframework.beans.factory.FactoryBean的工厂类,用户可以通过实现该接口定制化实例化bean逻辑的简单方案。
以下是源码:
public interface FactoryBean {
@Nullable
T getObject() throws Exception;
@Nullable
Class getObjectType();
default boolean isSingleton() {
return true;
}
}
定义了三个方法:
T getObject():返回FactoryBaen创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中。
Class getObjectType():返回FactoryBean创建的bean类型。
boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。
具体解释下:
当配置文件中的class属性配置的实现类是FactoryBean时,
通过getBean()方法放回的不是FactoryBean本身,
而是FactoryBean#getObject()方法返回的对象,相当于FactoryBean#getObject()代理了getBean()。
从缓存中获取单例bean
接下来我们就可以了解bean的加载过程了。
下面就是我们重要的原理了:
单例在Spring的同一个容器内只会被创建一次,
后续需要再获取bean时直接从单例缓存中获取即可,
当然这里也只是尝试创建,首先尝试从缓存中加载,
然后再次尝试从singletonFactories中加载。
这样加载的原因是:
因为在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,
Spring创建bean的原则是不等bean创建完成就会将创建的ObjectFactory提早曝光加入到缓存中,
一旦下一个bean创建需要依赖到上一个bean时,就可以直接使用ObjectFactory。
public Object getSingleton(String beanName) {
//参数true设置标识运行早期依赖
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//如果为空,则锁定全局变量并进行处理
synchronized (this.singletonObjects) {
//如果当前bean正在加载则不处理
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//当某些方法需要提前初始化的时候则会调用addSingletonFactory方法将对应的
//ObjectFactory初始化策略存储在singletonFactories中
//这里就是将存储的ObjectFactory取出来操作。
ObjectFactory// singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//调用预先设定的getObject方法
singletonObject = singletonFactory.getObject();
//记录在缓存中,earlySingletonObjects和singletonFactories互斥
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
接下来解读下上面的源代码
在这个方法中我们就已经涉及到了关键的循环依赖检测了同时也涉及很多变量的记录存取。
方法分下面步骤:
1.首先尝试从singletonObject里面获取实例,
2.如果获取不到就会从earlySingletonObjects里面获取,
3.如果还是获取不到,才会从singletonFactories里面获取beanName对应的ObjectFactory,
调用这个ObjectFactory的getObjiect来创建bean,
4.将bean放到earlySingletonObjects里面去,同时从singletonFactories里面remove
掉上面获取到的ObjectFactory,
而对于后续所有内存操作都只是为了循环依赖检测时使用,也就是源码中的allowEarlyReference为true的情况下才会使用。
上面涉及到多个存储bean的map,就是我们常说的3级缓存。
到这我们可以说:Spring就是通过三级缓存来解决循环依赖的。
/*1级缓存,单例对象的缓存:bean名称——> bean实例/
private final Map singletonObjects = new ConcurrentHashMap<>(256);
/** 2级缓存,早期单例对象的缓存:bean名称——> bean实例 */
private final Map earlySingletonObjects = new HashMap<>(16);
/** 3级缓存,单例工厂的缓存:bean名称——> ObjectFactory*/
private final Map/>/ singletonFactories = new HashMap<>(16);
singletonObjects:用于保存BeanName和创建bean实例之前的关系,bean name–>bean instance。
earlySingletonObjects:也是保存BeanName和创建bean实例之前的关系,
但是与singletonObjects的不同之处在于,当一个单例bean被加入这里之后,
即使bean还在创建过程中,也可以通过getBean方法获取到了,其目的是用来检测循环引用。
singletonFactories:用于保存BeanName和创建bean的工厂之间的关系,
bean name–>ObjectFactory。
写在后面的话
到这里我们已经知道了解决方案是Spring的3级缓存,通过提前暴露创建bean的ObjectFactory工厂,将其加入缓存,
获取单例的时候优先在去缓存中找,如果没有找到再进行bean的创建。
某些方法需要提前初始化的时候则会调用addSingletonFactory方法将对应的ObjectFactory初始化策略存储在singletonFactories中,
至于如何将bean初始化,在什么时候初始化,如果缓存中没有bean时会怎么办?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至22018681@qq.com 举报,一经查实,本站将立刻删除。