zoukankan      html  css  js  c++  java
  • 自己实现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。

  • 相关阅读:
    利用反馈字段给帝国cms添加留言板功能(图文教程)
    对程序员的不尊重是中国it产业的悲哀。
    网站原创文章被盗用怎么办?
    Vector
    iptables
    spark geoip
    geoip scala api
    matlab解三元二次方程组
    统计一个目录下所有普通文本文件的总行数
    awk多模式匹配
  • 原文地址:https://www.cnblogs.com/vitosun/p/4973733.html
Copyright © 2011-2022 走看看