一、什么是循环依赖
循环依赖就是在多个bean中,相互持有对方,导致在创建的时候无法加载。不如:beanA引用了beanB,beanB又应用了beanC,beanC最后又引用回了beanA,成了一个无限的循环。循环依赖是对象与对象之间才会发生的,而方法之间的相互调用的情况,叫做循环调用,此招无解最终会因为方法之间调用过多导致内存溢出。
在代码中表现大概就是如下的情况:
public class BeanA {
private BeanB beanB;
public BeanB getBeanB() {return beanB;}
public void setBeanB(BeanB beanB) {this.beanB = beanB;}
}
public class BeanB {
private BeanC beanC;
public BeanC getBeanC() {return beanC;}
public void setBeanC(BeanC beanC) {this.beanC = beanC;}
}
public class BeanC {
private BeanA beanA;
public BeanA getBeanA() {return beanA;}
public void setBeanA(BeanA beanA) {this.beanA = beanA;}
}
二、spring是如何解决循环依赖的问题
实际spring将循环依赖细分为了三种,并且spring只能解决在setter下的循环依赖,而无法解决其他两种情况。
-
构造器循环依赖
-
setter循环依赖
-
prototype作用域循环依赖
在细看三者之前,回顾一下第一篇文章中我们在获取单例Bean的时候有接触到的三个容器。如果不记得了最好回头看一下,他们在获取单例bean的时候发挥的作用。
-
singletonFactories : 单例对象工厂的缓存
-
earlySingletonObjects :提前暴光的单例对象的缓存
-
singletonObjects:单例对象的缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
1.构造器循环依赖
通过构造器注入构成的循环依赖是无法解决的,spring会通过抛出BeanCurrentlyInCreationException异常表示出现了循环依赖。
spring容器将每一个正在创建的Bean的时候,就会将这个bean提前曝光出去,也就是将其标识存入earlySingletonObjects之中,这个bean在创建的过程中一个标识将一直存在于earlySingletonObjects之中,直到创建完成,才会从中删除。spring也是通过这个容器来发现是否出现了循环依赖,让在存入earlySingletonObjects缓存中的时候,发现该标识已经存在就可以判断出循环依赖发生了,自此抛出异常。创建步骤如下:
-
beanA创建
-
去earlySingletonObjects中查看是否存在
-
如果没有就继续构造器中需要的beanB
-
准备好之后同样的去earlySingletonObjects查看一下
-
很好也没有,继续准备beanB需要的beanC
-
beanC准备后之后,也需要再次到earlySingletonObjects中查看
-
beanC也不再其中,根据代码准备beanA
-
beanA完成之后,去看看beanA是否存在earlySingletonObjects容器之中
-
一看吓一跳,beanA已经在容器中准备工作了,赶紧抛出BeanCurrentlyInCreationException异常
2.setter循环依赖
在setter注入的时候发生的依赖循环是可以被解决的,但只能解决单例情况的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚刚完成构造器但还没有进行注入的bean来完成的。通过提前暴露一个单例工厂让其他的bean可以引用到该Bean。可以通过setAllowCircularReferences来禁止循环引用。文章后面我们来简单的实现一下setter注入时的循环依赖解决方法,先摸清具体步骤:
-
创建beanA的时候先使用无参构造方法,并且暴露一个ObjectFactory用于返回一个创建中bean,依旧会去查看是否存在earlySingletonObjects之中,生成的工厂会被存放到singletonFactories之中。
-
继续创建beanB,和beanA的操一致
-
创建beanC之后,尝试通过setter注入beanA,这时候就会发现beanA依旧存在了,由于之前创建了beanA的ObjectFactory,可以通过这个工厂返回已经创建好的beanA。
3.prototype作用域循环依赖
这个就比较简单了,spring在prototype作用域下是无法完成循环注入的,spring容器不会对存在prototype的bean进行缓存,也就无法向单例的情况下提前暴露创建中的bean来解决依赖问题。
三、模仿实现自己的循环依赖解决
以下代码转载自https://blog.csdn.net/cl534854121/article/details/82881910
1.创建两个相互依赖的对象
public class BeanA {
private BeanB beanB;
public BeanB getBeanB() {return beanB;}
public void setBeanB(BeanB beanB) {this.beanB = beanB;}
}
public class BeanB {
private BeanA beanA;
public BeanA getBeanA() {return beanA;}
public void setBeanA(BeanA beanA) {this.beanA = beanA;}
}
2.创建ObjectFactory
public class ObjectFactory {
/**用于缓存正在初始化中的对象,同时作为提前暴露的缓存*/
private static final Map<Class, Object> currentInitObjects = new ConcurrentHashMap<>();
/**用于缓存初始化好的单例对象*/
private static final Map<Class, Object> objects = new ConcurrentHashMap<>();
/**
* 获取对象,并设值对象属性。
* 1. 不考虑并发问题,简单的示例
* 2. 解决单例setter循环依赖
*/
public <T> T getObject(Class<T> cls) {
//如果已经初始化过直接返回
if (objects.containsKey(cls)) {
return (T) objects.get(cls);
}
try {
T t;
//1. 简单的使用构造函数创建对象,并提前暴露到currentInitObjects中
t = cls.newInstance();
//提前暴露到currentInitObjects中
currentInitObjects.put(cls, t);
//2. 解决依赖属性值
resolveDependence(t, cls);
//3. 放入单例缓存中
objects.put(cls, t);
return t;
} catch (Exception e) {
System.out.println("初始化对象失败:" + cls);
return null;
} finally {
//4. 从正在初始化缓存中移除
currentInitObjects.remove(cls);
}
}
/**
* 解决依赖属性设值.
* @param object 对象
* @param cls 对象class
*/
private void resolveDependence(Object object, Class cls) {
//获取对象的属性,并进行赋值,省去了复杂的判断,就认为是对象
//1.获取所有属性
Field[] fields = cls.getDeclaredFields();
//2.循环处理属性值
Arrays.stream(fields).forEach(field -> {
field.setAccessible(true);
//2.1 获取属性class属性
Class fieldClass = field.getType();
Object value;
//2.2 判断是否已经初始化过
if (objects.containsKey(fieldClass)) {
value = objects.get(fieldClass);
} else if (currentInitObjects.containsKey(fieldClass)) {
//2.3 判断当前初始化的类中有没有这个属性.
value = currentInitObjects.get(fieldClass);
} else {
//2.4 如果都没有,进行初始化
value = getObject(fieldClass);
}
//3. 使用反射设置属性的值
try {
field.set(object, value);
} catch (IllegalAccessException e) {
System.out.println("设置对象属性失败:" + cls + "-" + field.getName());
}
});
}
}
3.创建测试类
public class Client {
public static void main(String[] args) {
ObjectFactory factory = new ObjectFactory();
ClassB classB = factory.getObject(ClassB.class);
ClassA classA = factory.getObject(ClassA.class);
}
}