两百行代码实现简单IoC容器

426 阅读4分钟

一.前言

Spring中有两大重要功能,一是AOP(面向切面编程),主要使用的是动态代理技术

Java基础(9)-反射与动态代理

二是IOC(控制反转)技术。控制反转即将对象的创建权交给Spring IOC容器来管理,开发者只需要注重顶层类的创建,不必理会底层类的各种依赖创建关系,而是将它们交由Spring IOC来创建。

本文将使用 反射技术 + dom4j解析技术 + xml配置文件 的方式实现简单的IOC操作,模拟IOC中使用setter方法注入的方式。

二.时序图

最后的反射获取实例操作,都通过XmlApplicationContext来实现;BeanUtils用于解析xml中的配置并配置好Bean和Property的映射关系 在这里插入图片描述

三.代码实现

  • 代码整体结构 核心代码位于XmlApplicationContext与BeanUtils中,一共两百多行代码 在这里插入图片描述

  • beans包 beans包存放了需要由IOC管理的bean

/**
 * @Auther: ARong
 * @Date: 2020/2/13 12:51 下午
 * @Description: Person类
 */
@Data
@ToString
public class Person {
    private String name;
    private Student student;
    public Person() {
        System.out.println(new Date() + ":Person类被创建");
    }
}


/**
 * @Auther: ARong
 * @Date: 2020/2/13 12:55 下午
 * @Description: Student类
 */
@Data
@ToString
public class Student {
    private String name;
    public Student() {
        System.out.println(new Date() + ":Student类被创建");
    }
}
  • xml包 xml包下存放着applicationContext.xml,IOC容器中所有的类都在这里进行注册和配置
<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean name="person" class="spring_demo.my_ioc.beans.Person" scope="prototype">
        <properties>
            <property name="name" value="ARong"/>
            <property name="student" ref="student"/>
        </properties>
    </bean>

    <bean name="student" class="spring_demo.my_ioc.beans.Student">
        <properties>
            <property name="name" value="XiaoMing"/>
        </properties>
    </bean>

</beans>
  • utils包 utils包下定义了Bean和Property,分别用来描述bean的所需类信息以及属性信息,方便后序使用反射进行创建bean实例

  • Bean

/**
 * @Auther: ARong
 * @Date: 2020/2/13 1:00 下午
 * @Description: 描述对象实例的抽象Bean
 */
@Data
@ToString
public class Bean {
    private String name;// 类名
    private String classpath;// 类全限定路径
    private String scope = "singleton";// 默认单例
    private List<Property> properties = new ArrayList<>();// 所含属性
}
  • Property
/**
 * @Auther: ARong
 * @Date: 2020/2/13 1:03 下午
 * @Description: 描述类所含属性的抽象
 */
@Data
@ToString
public class Property {
    private String name;// 属性名
    private String value;// 属性值
    private String ref;// 引用对象
}

另外,BeanUtils用于封装对于xml配置信息解析的方法

  • BeanUtils
/**
 * @Auther: ARong
 * @Date: 2020/2/13 1:09 下午
 * @Description: Bean操作的工具类
 */
public class BeanUtils {
    public static final BeanUtils DEFAULT = new BeanUtils();
    private BeanUtils() {}

    /*
     * @Author ARong
     * @Description 使用dom4j解析xml文件内容,封装对应的bean与相应属性
     * @Date 2020/2/13 1:12 下午
     * @Param [xmlPath]
     * @return java.util.Map<java.lang.String,spring_demo.my_ioc.utils.Bean>
     **/
    public Map<String, Bean> getBeanConfig(String xmlPath) {
        HashMap<String, Bean> map = new HashMap<>();
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(xmlPath);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Iterator<Element> beans = document.getRootElement().elementIterator();
        while (beans.hasNext()) {
            Element beanElement = beans.next();
            // 封装bean
            Bean bean = new Bean();
            bean.setName(beanElement.attributeValue("name"));
            bean.setClasspath(beanElement.attributeValue("class"));
            String scope = beanElement.attributeValue("scope");
            if (scope != null) {
                bean.setScope(scope);
            }
            // 封装属性
            ArrayList<Property> proList = new ArrayList<>();
            Iterator<Element> properties = beanElement.element("properties").elementIterator();
            while (properties.hasNext()) {
                Element propertyElement = properties.next();
                Property property = new Property();
                property.setName(propertyElement.attributeValue("name"));
                String value = propertyElement.attributeValue("value");
                String ref = propertyElement.attributeValue("ref");
                if (value != null) {
                    property.setValue(value);
                }
                if (ref != null) {
                    property.setRef(ref);
                }
                proList.add(property);
            }
            bean.setProperties(proList);
            map.put(bean.getName(), bean);
            // System.out.println(bean);
        }
        return map;
    }

}
  • context包 context包下定义了BeanFactory接口以及XmlApplicationContext实现类,用于作为IOC容器以及通过getBean方法获取类的实例
  • BeanFactory
public interface BeanFactory {
    /*
     * @Author ARong
     * @Description 根据name获取对象的实例
     * @Date 2020/2/13 1:57 下午
     * @Param [name]
     * @return java.lang.Object
     **/
    Object getBean(String name);
}
  • XmlApplicationContext
/**
 * @Auther: ARong
 * @Date: 2020/2/13 2:03 下午
 * @Description:
 */
@Data
public class XmlApplicationContext implements BeanFactory {
    private Map<String, Bean> beanMap;
    private static final Map<String, Object> context = new HashMap<>();

    private XmlApplicationContext() {
    }

    public XmlApplicationContext(String xmlPath) {
        beanMap = BeanUtils.DEFAULT.getBeanConfig(xmlPath);
        // 将单例对象先创建并放入context中
        Set<Map.Entry<String, Bean>> entries = beanMap.entrySet();
        for (Map.Entry<String, Bean> entry : entries) {
            String key = entry.getKey();
            Bean value = entry.getValue();
            if ("singleton".equals(value.getScope())) {
                context.put(key, createBean(value));
            }
        }
    }

    /*
     * @Author ARong
     * @Description 通过beanName获取Object
     * @Date 2020/2/13 2:08 下午
     * @Param [name]
     * @return java.lang.Object
     **/
    @Override
    public Object getBean(String name) {
        Object instance = context.get(name);
        if (instance == null) {
            // 未创建的多例对象,每次都新创建
            instance = createBean(beanMap.get(name));
        }
        return instance;
    }

    /*
     * @Author ARong
     * @Description 通过反射创建相应的对象
     * @Date 2020/2/13 2:13 下午
     * @Param [name, beanMap, context]
     * @return java.lang.Object
     **/
    public Object createBean(Bean bean) {
        // 获取类全限定名
        String classpath = bean.getClasspath();
        Class<?> beanClass = null;
        try {
            beanClass = Class.forName(classpath);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 创建class对象实例
        Object beanInstance = null;
        try {
            beanInstance = beanClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // 获得Bean的属性,将其注入
        if(bean.getProperties() != null){
            for(Property prop : bean.getProperties()){
                // 获得要注入的元素名称
                String proName = prop.getName();
                // 根据属性名称获得对应属性的set方法
                Method setMethod = getSetMethod(beanInstance, proName);

                Object param = null;
                if(prop.getValue() != null){
                    // value属性注入
                    param = prop.getValue();

                }
                if(prop.getRef() != null){
                    // bean引用注入
                    //要注入其他bean到当前bean中,先从容器中查找,当前要注入的bean是否已经创建并放入容器中
                    Object existBean = context.get(prop.getRef());
                    if(existBean == null){
                        // 容器中不存在要注入的bean,创建该bean
                        existBean = createBean(beanMap.get(prop.getRef()));
                        // 将创建好的单例bean放入容器中
                        if("singleton".equals(beanMap.get(prop.getRef()).getScope())) {
                            context.put(prop.getRef(), existBean);
                        }
                    }
                    param = existBean;
                }

                try {
                    // 调用set方法注入该属性
                    setMethod.invoke(beanInstance, param);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        return beanInstance;
    }

    /*
     * @Author ARong
     * @Description 通过反射获取到实例的变量setter方法
     * @Date 2020/2/13 2:32 下午
     * @Param [beanInstance, proName]
     * @return java.lang.reflect.Method
     **/
    private Method getSetMethod(Object beanInstance, String proName) {
        Class<?> beanClass = beanInstance.getClass();
        Method setterMathod = null;
        // 先获取方法参数类型
        Class<?> type = null;
        try {
            type = beanClass.getDeclaredField(proName).getType();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        String begin = proName.substring(0, 1).toUpperCase();
        String methodName = "set" + begin + proName.substring(1);
        // 获取setter方法
        try {
            setterMathod  = beanClass.getDeclaredMethod(methodName, type);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return setterMathod;
    }
}
  • test包 进行简单的IOC测试
  • TestIOC
/**
 * @Auther: ARong
 * @Date: 2020/2/13 1:24 下午
 * @Description: 测试IOC容器
 */
public class TestIoC {
    @Test
    public void testIOC() {
        // 1.初始化IOC容器
        System.out.println("==========1.初始化IOC容器==========");
        XmlApplicationContext context = new XmlApplicationContext("/Users/arong/MyFile/Project/Algorithm/src/main/java/spring_demo/my_ioc/xml/applicationContext.xml");

        // 2.测试注入在Student中注入name属性
        System.out.println("==========2.测试注入在Student中注入name属性==========");
        testGetBean1(context);

        // 3.测试在Person中注入student引用以及name属性
        System.out.println("==========3.测试在Person中注入student引用以及name属性==========");
        testGetBean2(context);

        // 4.测试Person对象是否为多例、Student对象是否为单例
        System.out.println("==========4.测试Person对象是否为多例、Student对象是否为单例==========");
        testGetBean3(context);
    }



    private void testGetBean1(XmlApplicationContext context) {
        Student student = (Student) context.getBean("student");
        System.out.println(student+"\n");
    }

    private void testGetBean2(XmlApplicationContext context) {
        Person person = (Person) context.getBean("person");
        System.out.println(person+"\n");
    }

    private void testGetBean3(XmlApplicationContext context) {
        Student student1 = (Student) context.getBean("student");
        Student student2 = (Student) context.getBean("student");
        System.out.println("student1 == student2:" + (student1 == student2));
        Person person1 = (Person) context.getBean("person");
        Person person2 = (Person) context.getBean("person");
        System.out.println("person1 == person2:"+(person1 == person2));
    }

输出结果显示,IOC基本功能正常,这两百多行代码就是IOC的核心代码了。具体的例如Bean的生命周期如何管理?以及如何配置注解的扫描方式就是更进一步的细化了~ 在这里插入图片描述