自己实现spring IOC

自己实现一个简易版的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。

posted @ 2015-11-18 10:39  若拙  阅读(502)  评论(0编辑  收藏  举报