首页 > 代码库 > 自己实现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的测试方法就没问题了

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    自己实现Spring IoC容器(四)IoC容器的Bug