为bean注入bean
根据github 项目学习:tiny-spring,地址:https://github.com/code4craft/tiny-spring
参考博客链接:https://blog.csdn.net/w8253497062015/article/details/90274387
目前的问题:无法处理bean之间的依赖,无法将bean注入到bean中,所以它无法称之为完整的IoC容器。
1.ref怎么实现?2.怎么解决xml中顺序问题?2.怎么避免循环依赖?
我们定义一个BeanReference
,来表示这个属性是对另一个bean的引用。这个在读取xml的时候初始化,并在初始化bean的时候,进行解析和真实bean的注入。
public class BeanReference {
private String name;
private Object bean;
public BeanReference(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getBean() {
return bean;
}
public void setBean(Object bean) {
this.bean = bean;
}
}
实现 bean 的 ref
判断xml中是ref还是value,如果是value(本项目目前value如果是基本类型,只允许是String)则直接用PV(PropertyValue)封装,如果是ref,就用BeanReference{name,bean}封装一下然后再用PV封装。
private void processProperty(Element ele, BeanDefinition beanDefinition) {
NodeList propertyNode = ele.getElementsByTagName("property");
for (int i = 0; i < propertyNode.getLength(); i++) {
Node node = propertyNode.item(i);
if (node instanceof Element) {
Element propertyEle = (Element) node;
String name = propertyEle.getAttribute("name");
String value = propertyEle.getAttribute("value");
if (value != null && value.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("Configuration problem: <property> element for property '"
+ name + "' must specify a ref or value");
}
BeanReference beanReference = new BeanReference(ref);
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
}
}
}
}
在调用applyPropertyValues()方法——通过反射装填实例的成员变量时,如果该变量是BeanReference,则该变量有可能需要创建一下。
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
Object value = propertyValue.getValue();
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getName());
}
declaredField.set(bean, value);
}
}
读取xml后,所有的类信息都在 XmlBeanDefinitionReader 实例中,但是 XmlBDFR 中的 beanDefinition 们并没有创建实例,即空有类信息(className,PropertyValues),但是bean为null。
此时,如果遇到 A 实例 a 的 b 字段 ref C 实例 c,但是此刻 C 实例 c 还未初始化,在装配 A 实例a的b字段的时候,就会用 getBean 创建c。(为什么能创建c呢?因为在创建工厂后,紧接着的操作就是把xmlBDFR中的所有beanDefinition写入工厂的 ConcurrentHashMap 中,即工厂也有了全部的信息,因此可以创建c。)
通过getBean时创建实例的这种 lazy-init
方式,实现了不依靠 xml 中顺序。这样再创建实例的时候如果实例的依赖还没有创建,就先创建依赖。
循环依赖是类似以下的情况
<bean name="outputService" class="com.sonihr.beans.OutputService">
<property name="helloWorldService" ref="helloWorldService"></property>
</bean>
<bean name="helloWorldService" class="com.sonihr.beans.HelloWorldServiceImpl">
<property name="text" value="Hello World!"></property>
<property name="outputService" ref="outputService"></property>
</bean>
在 doCreateBean 中,创建完空的bean (空的bean表示空构造函数构造出的bean) 后,就放入 beanDefinition 中,这样 a ref b,b ref a时,a ref b 因此 b 先创建并指向a,此时的 a 还不是完全体,但是引用已经连上了,然后创建好了b。然后b ref a的时候,a已经创建完毕。