Spring Ioc-解决循环依赖
# 一、Bean之间的依赖问题
# 1、XML配置添加引用
先前我们的属性注入都是基本类型,如果注入的属性本身就是一个对象,这就要涉及Bean之间的依赖问题。
Spring在标签里增加了ref属性
,这个属性就记录了需要引用的另一个Bean,参考如下配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id = "AService" class="me.xu.spring.test.demo02.AServiceImpl">
<property name="content" type="String" value="hello A"/>
</bean>
<bean id = "BService" class="me.xu.spring.test.demo02.BServiceImpl">
<constructor-arg name="content" type="String" value="hello b"/>
<constructor-arg name="type" type="int" value="1"/>
<property name="aService" type="me.xu.spring.test.demo02.AServiceImpl" ref="AService"></property>
</bean>
</beans>
首先我们定义了一个id为AService的Bean,然后我们在BService中设置ref="AService",表明我们希望注入的Bean不是一个简单的值,而是一个对象。
# 2、属性配置类添加引用
接下来需要在属性配置类添加引用字段,让其判断属性是引用类型还是普通类型。
public class PropertyValue {
private final String name;
private final String type;
private final Object value;
private final boolean isRef;
public PropertyValue(String type, String name, Object value, boolean isRef) {
this.type = type;
this.name = name;
this.value = value;
this.isRef = isRef;
}
public String getType() {
return this.type;
}
public String getName() {
return this.name;
}
public Object getValue() {
return this.value;
}
public boolean isRef() {
return isRef;
}
}
# 3、解析XML额外处理引用
// 处理属性
List<Element> propertyElements = element.elements("property");
PropertyValues propertyValues = new PropertyValues();
// Bean的依赖集合
List<String> refs = new ArrayList<>();
for (Element e : propertyElements) {
// 获取属性
String name = e.attributeValue("name");
String type = e.attributeValue("type");
String value = e.attributeValue("value");
// 获取依赖
String ref = e.attributeValue("ref");
// 真实value
String pV = "";
boolean isRef = false;
if (value != null && !"".equals(value)) {
pV = value;
} else if (ref != null && !"".equals(ref)) {
isRef = true;
pV = ref;
refs.add(ref);
}
// 添加到属性集合中
propertyValues.addPropertyValue(new PropertyValue(type, name, pV, isRef));
}
// 设置配置属性集合
beanDefinition.setPropertyValues(propertyValues);
String[] refArray = refs.toArray(new String[0]);
// 设置依赖集合
beanDefinition.setDependsOn(refArray);
- 判断注入的属性是引用还是基本类型
- 如果是基本类型,还是按照之前的处理逻辑
- 如果是引用类型,就需要将具体Bean的真实value修改为引用类型,并且设置依赖集合
# 4、Bean注入配置额外处理引用
我们将之前createBean
处理逻辑,单独抽离出来成为一个新方法:
private void handleProperty(BeanDefinition beanDefinition, Class<?> clz, Object obj) {
// 处理属性
PropertyValues propertyValues = beanDefinition.getPropertyValues();
if (!propertyValues.isEmpty()) {
// 遍历属性集合
for (int i = 0; i < propertyValues.size(); i++) {
PropertyValue propertyValue = propertyValues.getPropertyValueList().get(i);
String pName = propertyValue.getName();
String pType = propertyValue.getType();
Object pValue = propertyValue.getValue();
boolean isRef = propertyValue.isRef();
Class<?>[] paramTypes = new Class<?>[1];
Object[] paramValues = new Object[1];
// 如果是基本类型
if (!isRef) {
// 参数类型处理
if ("String".equals(pType) || "java.lang.String".equals(pType)) {
paramTypes[0] = String.class;
} else if ("Integer".equals(pType) || "java.lang.Integer".equals(pType)) {
paramTypes[0] = Integer.class;
} else if ("int".equals(pType)) {
paramTypes[0] = int.class;
} else {
paramTypes[0] = String.class;
}
// 参数具体数值
paramValues[0] = pValue;
} else {
// 如果是引用类型
try {
paramTypes[0] = Class.forName(pType);
// 再次调用getBean方法
paramValues[0] = getBean((String)pValue);
} catch (ClassNotFoundException | BeansException e) {
throw new RuntimeException(e);
}
}
// 方法名字
String methodName = "set" + pName.substring(0, 1).toUpperCase() + pName.substring(1);
Method method = null;
try {
// 获取到方法
method = clz.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
try {
// 反射调用方法
method.invoke(obj, paramValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
在这个方法中,除了之前对基本类型的处理逻辑外,额外增加对引用类型的处理逻辑,再次调用getBean创建ref的bean实例。
# 二、Spring循环依赖
# 1、循环依赖概述
在上面案例中,我们通过再次调用getBean方法,来对引用属性进行注入,但是如果引用注入的属性,又引用调用者本身,例如A注入B,B引用A,这样就会发生死循环,发生所谓的循环依赖问题,具体可以参考Spring循环依赖问题 (opens new window)。
# 2、解决思路
首先回忆一下创建Bean的过程:
- 根据XML的Bean的定义生成BeanDefinition
- 然后根据定义加载Bean类
- 实例化Bean类
- 完成属性注入
在属性注入之前,Bean的实例已经生成了,只是部分的属性没有注入,可以说现在这个Bean是一个半成品的Bean,最终注入完成的Bean,是放在单例对象集合中,也就是我们的singletons
中,在实际的Spring中对应一级缓存,存放单例Bean对象集合。
现在为了解决循环依赖问题,我们需要一个集合来存放早期的Bean对象,后续如果存在循环依赖问题,就不再去实例化Bean对象,而是去获取早期的Bean对象,对应实际Spring中的三级缓存。
# 3、具体方案
添加半成品Bean集合
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory, BeanDefinitionRegistry {
/**
* Bean半成品
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
}
相关方法
public Object getBean(String beanName) throws BeansException {
// 先尝试直接单例获取
Object singleton = getSingleton(beanName);
// 如果没有,开始实例化Bean
if (singleton == null) {
// 尝试从Bean半成品中获取
singleton = earlySingletonObjects.get(beanName);
if (singleton == null) {
// 如果半成品也没有,创建Bean实例并注册
// 获取Bean定义
BeanDefinition beanDefinition = beanDefinitions.get(beanName);
if (beanDefinition == null) {
throw new BeansException("错误Bean id");
}
try {
// 反射实例化Bean
singleton = createBean(beanDefinition);
// 注册单例Bean
registerSingleton(beanName, singleton);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return singleton;
}
- 先尝试直接单例获取
- 如果没有,开始实例化Bean
- 尝试从半成品Bean中获取
- 如果半成品也没有,创建Bean实例并注册
private Object createBean(BeanDefinition beanDefinition) {
// 类名
Class<?> clz = null;
// 创建半成品Bean
Object obj = doCreateBean(beanDefinition);
// 放入半成品Bean集合中
earlySingletonObjects.put(beanDefinition.getId(), obj);
try {
// 反射获取类名
clz = Class.forName(beanDefinition.getClassName());
} catch (Exception e) {
e.printStackTrace();
}
// 处理配置参数(属性注入)
handleProperty(beanDefinition, clz, obj);
return obj;
}
- 创建半成品Bean
- 将半成品Bean放入
earlySingletonObjects
集合中 - 调用
handleProperty
方法进行属性注入
private Object doCreateBean(BeanDefinition beanDefinition) {
Class clz = null;
Object obj = null;
Constructor constructor = null;
// 处理构造器参数
ArgumentValues argumentValues = beanDefinition.getConstructorArgumentValues();
if (!argumentValues.isEmpty()) {
// 参数类型
Class<?>[] paramTypes = new Class<?>[argumentValues.getArgumentCount()];
// 参数值
Object[] paramValues = new Object[argumentValues.getArgumentCount()];
// 遍历构造器参数集合
for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
ArgumentValue argumentValue = argumentValues.getIndexedArgumentValue(i);
if ("String".equals(argumentValue.getType()) || "java.lang.String".equals(argumentValue.getType())) {
paramTypes[i] = String.class;
paramValues[i] = argumentValue.getValue();
} else if ("Integer".equals(argumentValue.getType()) || "java.lang.Integer".equals(argumentValue.getType())) {
paramTypes[i] = Integer.class;
paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
} else if ("int".equals(argumentValue.getType())) {
paramTypes[i] = int.class;
paramValues[i] = Integer.valueOf((String) argumentValue.getValue()).intValue();
} else {
paramTypes[i] = String.class;
paramValues[i] = argumentValue.getValue();
}
}
try {
// 获取构造函数
constructor = clz.getConstructor(paramTypes);
// 利用构造函数反射实例化Bean
obj = constructor.newInstance(paramValues);
} catch (NoSuchMethodException | InvocationTargetException | IllegalArgumentException | SecurityException e) {
e.printStackTrace();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else {
try {
obj = clz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return obj;
}
- 专门负责生成半成品Bean
现在我们再来回顾一下getBean()
方法:
- 首先要判断有没有已经创建好的 bean,有的话直接取出来,
- 如果没有就检查 earlySingletonObjects 中有没有相应的半成品 Bean,
- 有的话直接取出来,没有的话就去创建,并且会根据 Bean 之间的依赖关系把相关的 Bean 全部创建好。
# 参考
上次更新: 2024/06/29, 15:13:44