1. 程式人生 > >自己實現Spring IoC容器(四)IoC容器的Bug

自己實現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的測試方法就沒問題了