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

来源:互联网 发布:asp.net crm系统源码 编辑:程序博客网 时间:2024/06/06 03:52

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的测试方法就没问题了

2 0
原创粉丝点击