上一章自己实现Spring IoC容器(二)读取配置文件我把读取配置文件这一块功能完成了,现在就可以根据配置文件来创建IoC容器了
#定义接口
先模仿一下Spring假装地定义一个BeanFactory
接口,虽然对于我这个项目意义不大
package edu.jyu.core;
public interface BeanFactory {
/**
* 根据name返回bean
* @param name
* @return
*/
Object getBean(String name);
}
#初始化容器
然后定义一个BeanFactory
的实现类ClassPathXmlApplicationContext
,这就是实现容器的类。然后我在这个类的构造方法中根据配置文件的信息将scope
值为singleton
的bean对象创建出来并放到容器中,但是不创建scope
值为prototype
的bean对象,而是在后面每次调用getBean
方法时创建一个新的bean对象。
ClassPathXmlApplicationContext类
package edu.jyu.core;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.beanutils.BeanUtils;
import edu.jyu.config.Bean;
import edu.jyu.config.Property;
import edu.jyu.config.parsing.ConfigurationManager;
public class ClassPathXmlApplicationContext implements BeanFactory {
// 存放配置文件信息
private Map<String, Bean> config;
// 存放bean对象的容器
private Map<String, Object> context = new HashMap<>();
/**
* 将配置文件中设置为单例的bean对象创建好放入容器中
*
* @param path
*/
public ClassPathXmlApplicationContext(String path) {
// 读取配置文件中bean的信息
config = ConfigurationManager.getBeanConfig(path);
// 遍历初始化bean
if (config != null) {
for (Entry<String, Bean> e : config.entrySet()) {
// 获取bean信息
String beanName = e.getKey();
Bean bean = e.getValue();
// 如果设置成单例的才创建好bean对象放进容器中
if (bean.getScope().equals(Bean.SINGLETON)) {
Object beanObj = createBeanByConfig(bean);
context.put(beanName, beanObj);
}
}
}
}
/**
* 根据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(config.get(prop.getRef()));
//下面这句代码写错了,现在修改过来
//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;
}
@Override
public Object getBean(String name) {
return null;
}
}
在构造方法中,先是通过ConfigurationManager
获取配置文件信息,然后根据配置信息找出scope
值为singleton
的bean标签并创建相应的bean对象,创建好了并放入存放bean对象的容器context
中。需要注意的是createBeanByConfig
方法中存在着递归调用,因为可能在创建一个bean对象的时候它所依赖的bean对象还没有被创建,所以要先创建它依赖的bean对象。
#实现getBean方法
初始化容器后,现在来实现一下BeanFactory
的getBean
方法
public Object getBean(String name) {
Bean bean = config.get(name);
Object beanObj = null;
if (bean.getScope().equals(Bean.SINGLETON)) {
// 如果将创建bean设置成单例则在容器中找
beanObj = context.get(name);
} else if (bean.getScope().equals(Bean.PROTOTYPE)) {
// 如果是prototype则新创建一个对象
beanObj = createBeanByConfig(bean);
}
return beanObj;
}
这个方法的注释已经写的很明白了,就不再重复说了。
#测试
现在整个项目大致完成了,接下来就来测试一下
测试类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(b.getAName() + ":" + b.getAge());
System.out.println("a==a1 : "+(a==a1));
System.out.println("b==b1 : "+(b==b1));
}
}
下面这句代码想要测试的是A类对象是否被注入到B中,还有A类对象的name
属性是否注入了Jason
字符串,B类对象的age
是否被注入了13
。
System.out.println(b.getAName() + ":" + b.getAge());
下面两句代码想要测试的是scope
的两个值singleton
和prototype
对应的语义是否实现,比如A中定义的scope值是singleton(没有写默认就是singleton),那么每次获得的A类对象都是同一个对象。B中定义的scope值是prototype,那么每次获得的B类对象都是重新创建的一个对象。
System.out.println("a==a1 : "+(a==a1));
System.out.println("b==b1 : "+(b==b1));
输出结果
Jason:13
a==a1 : true
b==b1 : false
从输出结果来看,功能没错。
#结语
我自己实现的IoC容器无疑是十分简(jian)洁(lou)的,有很多健壮性判断也没有,一切都是按照理想的情况实现,所以不能直接拿来用,但是IoC原理还是体现了。作为一个山寨大王,以后我还会继续山寨一些框架,比如过段时间我准备山寨Spring MVC~
好了,整个IoC容器大功告成,项目已经上传到Github上
https://github.com/HuangFromJYU/JSpring-IoC
如果大家有什么问题或者发现什么错误可以发邮件到jasonwong_hjj@qq.com,共同学习共同进步