什么是循环依赖?
顾名思义,循环依赖就是A依赖B,B又依赖A,两者之间的依赖关系形成了一个圆环,通常是由于不正确的编码所导致。Spring只能解决属性循环依赖问题,不能解决构造函数循环依赖问题,因为这个问题无解。
接下来我们首先写一个Demo来演示Spring是如何处理属性循环依赖问题的。
Talk is cheap. Show me the code
第一步:定义一个类ComponentA,其有一个私有属性componentB。
package com.tech.ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author 君战
* **/
@Component
public class ComponentA {
@Autowired
private ComponentB componentB;
public void say(){
componentB.say();
}
}
第二步:定义一个类ComponentB,其依赖ComponentA。并定义一个say方法便于打印数据。
package com.tech.ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author 君战
* **/
@Component
public class ComponentB {
@Autowired
private ComponentA componentA;
public void say(){
System.out.println("componentA field " + componentA);
System.out.println(this.getClass().getName() + " -----> say()");
}
}
第三步:重点,编写一个类-SimpleContainer,模仿Spring底层处理循环依赖。如果理解这个代码,再去看Spring处理循环依赖的逻辑就会很简单。
package com.tech.ioc;
import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 演示Spring中循环依赖是如何处理的,只是个简版,真实的Spring依赖处理远比这个复杂。
* 但大体思路都相同。另外这个Demo很多情况都未考虑,例如线程安全问题,仅供参考。
* @author 君战
*
* **/
public class SimpleContainer {
/***
* 用于存放完全初始化好的Bean,Bean处于就绪状态
* 这个Map定义和Spring中一级缓存命名一致
* */
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
/***
* 用于存放刚创建出来的Bean,其属性还没有处理,因此存放在该缓存中的Bean还不可用。
* 这个Map定义和Spring中三级缓存命名一致
* */
private final Map<String, Object> singletonFactories = new HashMap<>(16);
public static void main(String[] args) {
SimpleContainer container = new SimpleContainer();
ComponentA componentA = container.getBean(ComponentA.class);
componentA.say();
}
public <T> T getBean(Class<T> beanClass) {
String beanName = this.getBeanName(beanClass);
// 首先根据beanName从缓存中获取Bean实例
Object bean = this.getSingleton(beanName);
if (bean == null) {
// 如果未获取到Bean实例,则创建Bean实例
return createBean(beanClass, beanName);
}
return (T) bean;
}
/***
* 从一级缓存和二级缓存中根据beanName来获取Bean实例,可能为空
* */
private Object getSingleton(String beanName) {
// 首先尝试从一级缓存中获取
Object instance = singletonObjects.get(beanName);
if (instance == null) { // Spring 之所以能解决循环依赖问题,也是靠着这个三级缓存--singletonFactories
instance = singletonFactories.get(beanName);
}
return instance;
}
/***
* 创建指定Class的实例,返回完全状态的Bean(属性可用)
*
* */
private <T> T createBean(Class<T> beanClass, String beanName) {
try {
Constructor<T> constructor = beanClass.getDeclaredConstructor();
T instance = constructor.newInstance();
// 先将刚创建好的实例存放到三级缓存中,如果没有这一步,Spring 也无法解决三级缓存
singletonFactories.put(beanName, instance);
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
Class<?> fieldType = field.getType();
field.setAccessible(true);
// 精髓是这里又调用了getBean方法,例如正在处理ComponentA.componentB属性,
// 执行到这里时就会去实例化ComponentB。因为在getBean方法首先去查缓存,
// 而一级缓存和三级缓存中没有ComponentB实例数据,所以又会调用到当前方法,
// 而在处理ComponentB.componentA属性时,又去调用getBean方法去缓存中查找,
// 因为在前面我们将ComponentA实例放入到了三级缓存,因此可以找到。
// 所以ComponentB的实例化结束,方法出栈,返回到实例化ComponentA的方法栈中,
// 这时ComponentB已经初始化完成,因此ComponentA.componentB属性赋值成功!
field.set(instance, this.getBean(fieldType));
}
// 最后再将初始化好的Bean设置到一级缓存中。
singletonObjects.put(beanName, instance);
return instance;
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalArgumentException();
}
/**
* 将类名小写作为beanName,Spring底层实现和这个差不多,也是使用javaBeans的
* {@linkplain Introspector#decapitalize(String)}
**/
private String getBeanName(Class<?> clazz) {
String clazzName = clazz.getName();
int index = clazzName.lastIndexOf(".");
String className = clazzName.substring(index);
return Introspector.decapitalize(className);
}