自己實現Spring IoC容器(四)IoC容器的Bug
Bug的發現
之前我自己寫了一個類似Spring中的IoC容器 自己實現Spring IoC容器(三)完成IoC容器,然後最近想在這個專案基礎上把Spring的AOP也實現一下,然後就悲劇的發現了一句錯誤程式碼……
這個錯誤程式碼就在edu.jyu.core.ClassPathXmlApplicationContext
類的Object createBeanByConfig(Bean bean)
方法中,下面是這個方法
/**
* 根據bean的配置資訊建立bean物件
*
* @param bean
* @return
*/
private Object createBeanByConfig (Bean bean) {
// 根據bean資訊建立物件
Class clazz = null;
Object beanObj = null;
try {
clazz = Class.forName(bean.getClassName());
// 建立bean物件
beanObj = clazz.newInstance();
// 獲取bean物件中的property配置
List<Property> properties = bean.getProperties();
// 遍歷bean物件中的property配置,並將對應的value或者ref注入到bean物件中
for (Property prop : properties) {
Map<String, Object> params = new HashMap<>();
if (prop.getValue() != null) {
params.put(prop.getName(), prop.getValue());
// 將value值注入到bean物件中
BeanUtils.populate(beanObj, params);
} else if (prop.getRef() != null) {
Object ref = context.get(prop.getRef());
// 如果依賴物件還未被載入則遞迴建立依賴的物件
if (ref == null) {
ref = createBeanByConfig(bean);
}
params.put(prop.getName(), ref);
// 將ref物件注入bean物件中
BeanUtils.populate(beanObj, params);
}
}
} catch (Exception e1) {
e1.printStackTrace();
throw new RuntimeException("建立" + bean.getClassName() + "物件失敗");
}
return beanObj;
}
錯誤就在如果依賴物件還未被載入條件成立後,ref = createBeanByConfig(bean);
這句程式碼的問題是什麼了,很明顯我一不小心又把當前要建立的bean物件的配置資訊傳入createBeanByConfig
方法中了,所以就會無限遞迴下去,最後發生StackOverflowError
錯誤。
至於為什麼我的測試程式碼能通過也是比較湊巧,我的測試bean是一個A類,一個B類,其中B類依賴A類物件,所以我們要把A類物件注入到B類中,但是就是這麼巧,讀取配置檔案的時候先讀到了A類,所以在要建立B類物件時,A類物件已經建立好了,ref == null
就為false,也就是說沒執行到那句錯誤程式碼,所以就沒發現……
要是我改為A類依賴B類,那就可以發現問題了,因為要建立A類物件時,B類物件還沒建立。
A類
package edu.jyu.bean;
public class A {
private String name;
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
B類
package edu.jyu.bean;
public class B {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
此時配置檔案applicationContext.xml
也需要修改一下
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean name="A" class="edu.jyu.bean.A">
<property name="name" value="Jason"></property>
<property name="b" ref="B"></property>
</bean>
<bean name="B" class="edu.jyu.bean.B" scope="prototype">
<property name="age" value="13"></property>
</bean>
</beans>
測試類TestApplicationContext
也改一下
package edu.jyu.core;
import org.junit.Test;
import edu.jyu.bean.A;
import edu.jyu.bean.B;
public class TestApplicationContext {
@Test
public void test() {
BeanFactory ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
A a = (A) ac.getBean("A");
A a1 = (A) ac.getBean("A");
B b = (B) ac.getBean("B");
B b1 = (B) ac.getBean("B");
System.out.println(a.getB());
System.out.println("a==a1 : "+(a==a1));
System.out.println("b==b1 : "+(b==b1));
}
}
執行這個測試,你就會驚喜地發現爆棧了
Bug的解決
解決上面的那個Bug並不難,只需要把那句錯誤程式碼ref = createBeanByConfig(bean);
換成ref = createBeanByConfig(config.get(prop.getRef()));
完整方法
/**
* 根據bean的配置資訊建立bean物件
*
* @param bean
* @return
*/
private Object createBeanByConfig(Bean bean) {
// 根據bean資訊建立物件
Class clazz = null;
Object beanObj = null;
try {
clazz = Class.forName(bean.getClassName());
// 建立bean物件
beanObj = clazz.newInstance();
// 獲取bean物件中的property配置
List<Property> properties = bean.getProperties();
// 遍歷bean物件中的property配置,並將對應的value或者ref注入到bean物件中
for (Property prop : properties) {
Map<String, Object> params = new HashMap<>();
if (prop.getValue() != null) {
params.put(prop.getName(), prop.getValue());
// 將value值注入到bean物件中
BeanUtils.populate(beanObj, params);
} else if (prop.getRef() != null) {
Object ref = context.get(prop.getRef());
// 如果依賴物件還未被載入則遞迴建立依賴的物件
if (ref == null) {
//下面這句的錯誤在於傳入了當前bean配置資訊,這會導致不斷遞迴最終發生StackOverflowError
//解決辦法是傳入依賴物件的bean配置資訊
//ref = createBeanByConfig(bean);
ref = createBeanByConfig(config.get(prop.getRef()));
}
params.put(prop.getName(), ref);
// 將ref物件注入bean物件中
BeanUtils.populate(beanObj, params);
}
}
} catch (Exception e1) {
e1.printStackTrace();
throw new RuntimeException("建立" + bean.getClassName() + "物件失敗");
}
return beanObj;
}
現在執行測試類TestApplicationContext
的測試方法就沒問題了