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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值