自己实现一个简易版的ioc容器,通过xml配置文件来加载bean
首先编写需要用到的bean,这里写三个简单的bean分别是A,B,C
package com.bean; public class A { private String name; public A() { System.out.println("创建A"); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.bean; public class B { private A a; public B() { System.out.println("创建B"); } public A getA() { return a; } public void setA(A a) { this.a = a; } }
package com.bean; public class C { private B b; public C() { System.out.println("创建C"); } public B getB() { return b; } public void setB(B b) { this.b = b; } }
接下来编写配置文件 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean name="a" class="com.bean.A" > <property name="name" value="Tom"> </property> </bean> <bean name="b" class="com.bean.B" scope="prototype"> <property name="a" ref="a"> </property> </bean> <bean name="c" class="com.bean.C" scope="prototype"> <property name="b" ref="b"> </property> </bean> </beans>
然后是保存bean信息的类,bean信息包括:bean的name,class,scope,和property
这里的scope值只设定为singleton和prototype两种,spring中bean 的scope值默认为singleton,表示在同一个ioc容器中只有一个实例,而prototype则是每次getBean()都会得到一个新的实例。
property有可能是一个普通属性,也可能是引用另一个bean
package com.config; import java.util.ArrayList; import java.util.List; public class Bean { private String name; private String className; private String scope = "singleton";//scope默认为singleton private List<Property> properties = new ArrayList<Property>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public List<Property> getProperties() { return properties; } public void setProperties(List<Property> properties) { this.properties = properties; } //重新toString 方法 为了测试的时候可能明确看到读取的bean信息 @Override public String toString() { // TODO Auto-generated method stub return "Bean[name="+name+" class="+className+" scope="+scope+" property="+properties+"]"; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } }
package com.config; public class Property { private String name; private String value; //value表示普通的属性值 private String ref; //ref表示引用的其他bean的name public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getRef() { return ref; } public void setRef(String ref) { this.ref = ref; } }
读取和解析xml文件,得到bean的信息,将其封装为bean对象,返回一个Map<beanName,bean>
package com.config.parse; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.config.Bean; import com.config.Property; public class ConfigManager { //读取配置文件,并返回读取结果 public static Map<String,Bean> getConfig(String path){ Map<String,Bean> map = new HashMap<String,Bean>(); //dom4j 实现 //1.创建解析器 SAXReader reader = new SAXReader(); //2,加载配置文件=> document对象 InputStream is = ConfigManager.class.getResourceAsStream(path); Document doc = null; try { doc = reader.read(is); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } //3.定义xpath表达式,取出所有Bean对象 String xpath = "//bean"; //4.对Bean元素进行遍历 List<Element> list = doc.selectNodes(xpath); if(list != null){ for(Element beanEle : list){ //将bean元素的name/class 属性封装到Bean对象中 String name = beanEle.attributeValue("name"); String className = beanEle.attributeValue("class"); String scope = beanEle.attributeValue("scope"); Bean bean = new Bean(); bean.setName(name); bean.setClassName(className); if(null != scope){ //不设置scope值时,默认为singleton bean.setScope(scope); } //获得Bean元素下的所有property子元素,将属性封装到Property对象 List<Element> children = beanEle.elements("property"); if(children != null){ for(Element child : children){ Property prop = new Property(); String pName = child.attributeValue("name"); String pValue = child.attributeValue("value"); String pRef = child.attributeValue("ref"); prop.setName(pName); prop.setValue(pValue); prop.setRef(pRef); //将Property封装到Bean对象 bean.getProperties().add(prop); } } //将Bean对象封装到Map中(用于返回) map.put(name, bean); } } //5.返回Map结果 return map; } }
利用反射创建bean实例并将属性注入,在容器初始化的时候,先将所有作用域为singleton的bean加载到容器中,而prototype类型的bean不会保存在容器中,每次调用getBean时都会创建一个实例。
首先创建一个beanFactory
package com.main; public interface BeanFactory { //根据Bean的name获得Bean对象的方法 Object getBean(String beanName); }
实现了beanFactory,并创建容器和bean的类
package com.main; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import com.config.Bean; import com.config.Property; import com.config.parse.ConfigManager; import com.utils.BeanUtils; public class ClassPathXmlApplicationContext implements BeanFactory { //希望在ClassPathXmlApplicationContext类一创建 //就初始化spring容器(装载Bean实例) private Map<String, Bean> config; //获取的配置文件 //使用一个map来作为容器 private Map<String, Object> context = new HashMap<String, Object>(); @Override public Object getBean(String beanName) { //根据bean的名称获得bean实例 Object bean = context.get(beanName); //如果bean的scope属性为prototype,那么context中不会包含该bean,需要创建该bean并返回 if(null == bean){ bean= createBean(config.get(beanName)); } return bean; } public ClassPathXmlApplicationContext(String path) { //1.读取配置文件获得需要初始化的Bean信息 config = ConfigManager.getConfig(path); //2.遍历配置,初始化Bean if(config != null){ for(Entry<String, Bean> en : config.entrySet()){ //获取配置中的Bean信息 String beanName = en.getKey(); Bean bean = en.getValue(); Object existBean = context.get(beanName); //先判断容器中是否已存在该bean,因为createBean方法在创建并将一个bean加载到容器时,也会创建并加载它引用的bean if(existBean == null && bean.getScope().equals("singleton")){ //如果不存在,并且bean的scope属性为singleton时才将其放入容器中 //根据bean配置创建bean对象 Object beanObj = createBean(bean); // System.out.println(beanObj); //3.将初始的Bean放入容器 context.put(beanName, beanObj); } } } } private Object createBean(Bean bean){ //1.获得要创建的bean的Class String className = bean.getClassName(); Class clazz = null; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } //获得class后将class对应的对象创建出来 Object beanObj = null; try { beanObj = clazz.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } //2.获得Bean的属性,将其注入 if(bean.getProperties() != null){ for(Property prop : bean.getProperties()){ //获得要注入的元素名称(属性名或者bean名) String name = prop.getName(); //根据属性名称获得对应属性的set方法 Method setMethod = BeanUtils.getWriteMethod(beanObj, name); Object param = null; //注入分两种情况 if(prop.getValue() != null){ //1.value属性注入 //获得要注入的属性值,为了简单起见,这里没有考虑属性类型转换的问题,通常,从配置文件中直接读取的属性值都为String类型,还需要转换成具体Bean中所定义的类型 param = prop.getValue(); } if(prop.getRef() != null){ //2.其他bean的注入 //要注入其他bean到当前bean中,先从容器中查找,当前要注入的bean是否已经创建并放入容器中 Object existBean = context.get(prop.getRef()); if(existBean == null){ //容器中不存在要注入的bean,创建该bean existBean = createBean(config.get(prop.getRef())); //将创建好的单例bean放入容器中 if(config.get(prop.getRef()).getScope().equals("singleton")) context.put(prop.getRef(), existBean); } param = existBean; } try { //调用set方法注入该属性 setMethod.invoke(beanObj, param); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return beanObj; } }
上面用到了setXxx方法来注入属性,首先需要获得该类的setXxx方法,这里自己实现了一个简单的BeanUtils,也可以使用apache提供的BeanUtils
顺便提一下javaBean的一个命名规范,java的属性变量名都必须以小写字母开头,在特殊情况下也允许大写字母开头的属性变量名,不过必须满足“变量的前两个字母要么全部大写,要么全部小写”,例如iDcode这种命名是非法的,在sping中如果这样配置了属性名,将找不到setter方法。
package com.utils; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class BeanUtils { //参数1.bean对象 //参数2.要获得bean对象的属性名 public static Method getWriteMethod(Object beanObj, String name) { // TODO Auto-generated method stub Class<?> className = beanObj.getClass(); Method writeMethod = null; /*使用反射 Field field = null; try { field = className.getDeclaredField(name); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } String firstLetter = name.substring(0, 1).toUpperCase(); String methodName = "set"+firstLetter+name.substring(1); try { writeMethod = className.getDeclaredMethod(methodName, field.getType()); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } */ //使用内省introspector 内省(IntroSpector)是Java语言对JavaBean 类属性、事件的一种缺省处理方法 try { //1.分析bean对象,得到BeanInfo BeanInfo beanInfo = Introspector.getBeanInfo(className); //2.根据BeanInfo获得所有属性的描述器 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); //3.遍历描述器 if(descriptors != null){ for(PropertyDescriptor descriptor : descriptors){ //判断当前遍历的描述器所描述的属性是否是我们要找的属性 if(name.equals(descriptor.getName())){ //4.如果找到了,返回该属性的set方法,如果没有找到抛出异常,提醒用户创建该属性的set方法 writeMethod = descriptor.getWriteMethod(); break; } } } if(writeMethod == null){ new Throwable("请创建创建该"+name+"属性的set方法"); } } catch (IntrospectionException e) { // TODO Auto-generated catch block e.printStackTrace(); } return writeMethod; } }
接下来写一个测试类
package com.test; import java.util.Map; import com.bean.A; import com.bean.B; import com.bean.C; import com.config.Bean; import com.config.parse.ConfigManager; import com.main.BeanFactory; import com.main.ClassPathXmlApplicationContext; public class Test { @org.junit.Test public void fun(){ BeanFactory bf = new ClassPathXmlApplicationContext("/applicationContext.xml"); // A a = (A) bf.getBean("a"); // A a2 = (A) bf.getBean("a"); // A a3 = (A) bf.getBean("a"); // // // System.out.println(a.getName()); // // B b = (B) bf.getBean("b"); // B b2 = (B) bf.getBean("b"); // B b3 = (B) bf.getBean("b"); // System.out.println(b.getA().getName()); C c = (C) bf.getBean("c"); C c2 = (C) bf.getBean("c"); C c3 = (C) bf.getBean("c"); } }
输出的结果如下:
创建A
创建C
创建B
创建C
创建B
创建C
创建B
结果分析,其中A为singleton类型,所以在初始化时只创建了一次,B和C都是prototype类型,每次调用getBean时都会创建一次,其中C,引用了B,B引用了A,所以创建C的时候需要先检查B和A,这时A已经在初始化时创建了,所以每次创建C时都会创建一次B。