手写Spring之IOC基于xml动态创建对象

现库存的IOC代码写了两种实现方式,一种是基于xml文件动态创建对象的实现方式,一种是基于注解创建对象的实现方式,先把这篇基于xml文件创建对象的实现方式写出来,再去发注解的。

先发类图

在这里插入图片描述

先创建一个MAVEN工程,在pom.xml导入需要用到的依赖,在这里只需要导入解析xml文件的dom4j的依赖即可

  <!--解析xml-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

创建实体类

package com.spring.ioc.ioc_not_annotation.pojo;

public class User {
    private int id;
    private String name;
    private String password;
	
    public User() {
        System.out.println("无参构造函数执行");
    }
    //在这里就省略get和set方法了不贴出来了

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

接着创建user.xml文件,放到resources目录下

在这里插入图片描述

内容如下

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="user1" class="com.spring.ioc.ioc_not_annotation.pojo.User" scope="singleton">
        <property name="id" value="1"/>
        <property name="name" value="111"/>
        <property name="password" value="123456"/>
    </bean>
    <bean id="user2" class="com.spring.ioc.ioc_not_annotation.pojo.User" scope="prototype">
        <property name="id" value="2"/>
        <property name="name" value="222"/>
        <property name="password" value="654321"/>
    </bean>
</beans>

接下来开始创建核心的ClassPathXmlApplicationContext类。

在这里插入图片描述

首先定义几个后面需要用到的容器,在这里我用map来储存。

  //存储单例对象容器
    private Map<String, Object> singletonBeanFactory;
    //存储创建类定义对象的容器
    private Map<String, Class<?>> beanDefinationFactory;
    //存储beanElement 也就是xml文件里的bean节点
    private Map<String, Element> beanEleMap;
    //存储bean的scope属性容器
    private Map<String, String> beanScopeMap;

定义有参的构造函数,初始化内部容器,并调用初始化方法

   //定义有参的构造方法,在创建此类实例时,需要指定xml文件路径
    public ClassPathXmlApplicationContext(String xmlpath) {
        singletonBeanFactory = new HashMap<>();
        beanDefinationFactory = new HashMap<>();
        beanEleMap = new HashMap<>();
        beanScopeMap = new HashMap<>();
        init(xmlpath);
    }

init 初始化方法如下,每一行都加了详细代码,直接看代码就行

 /**
     * 初始化方法,在创建ClassPathXmlApplicationContext 对象时初始化容器
     * 并解析xml配置文件,获取bean元素,在运行时动态创建对象,并为对象的属性赋值
     * 最后吧对象存放在容器中
     *
     * @param xmlPath
     */
    private void init(String xmlPath) {
        //使用dom4j技术读取xml文档 创建saxreader对象
        SAXReader reader = new SAXReader();
        try {
            //获取读取xml配置文件的输入流
            InputStream is = getClass().getClassLoader().getResourceAsStream(xmlPath);
            //读取xml,该操作会返回一个Document对象
            Document document = reader.read(is);
            //获取文档的根元素
            Element rootElement = document.getRootElement();
            //获取根元素下所有的bean节点,element方法会返回元素的集合
            List<Element> beanElements = rootElement.elements("bean");

            //遍历bean节点
            for (Element beanEle : beanElements) {
                //获取bean节点的id,把该值做为key存储在map集合中
                String beanId = beanEle.attributeValue("id");
                //将beanElementd对象存入map中,为对象设置属性值时使用
                beanEleMap.put(beanId, beanEle);
                //获取bean节点的scope值
                String beanScope = beanEle.attributeValue("scope");
                //如果beanScope不等于null,将bean的scope值存入map中方便后续使用
                if (beanScope != null) {
                    beanScopeMap.put(beanId, beanScope);
                }
                //获取bean节点实体类对应的class路径
                String beanClassPath = beanEle.attributeValue("class");
                //利用反射根据class路径得到类定义对象
                Class<?> cls = Class.forName(beanClassPath);
                //如果反射获取的类定义对象不为null,则放入工厂中,方便创建实例对象
                if (cls != null) {
                    beanDefinationFactory.put(beanId, cls);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

初始化工作完成后,就可以开始写获取对象的getBean方法了

 /**
     * 根据传入的bean的id值获取容器中的对象,类型为Object
     * @param beanId
     * @return
     */
    public Object getBean(String beanId) {
        //根据传入的beanId获取类对象
        Class<?> cls = beanDefinationFactory.get(beanId);
        //根据id获取该bean对象的element对象
        Element element = beanEleMap.get(beanId);
        //获取scopeMap中bean元素的scope属性值
        String scope = beanScopeMap.get(beanId);
        Object object = null;
        try {
            //如果scope 等于singleton,或者scope为空 由于为空的话系统默认设置的作用域是singleton,创建单例对象;
            if ("singleton".equals(scope) || null == scope) {
                //判断容器中是否已有该对象的实例,如果没有,创建一个实例对象放到容器中
                if (singletonBeanFactory.get(beanId) == null) {
                    //创建反射加载的类的实例
                    Object instance = cls.newInstance();
                    //将创建后的对象放入实例对象容器中 beanid为xml配置文件的bean节点的id 也就是实例对象的名字
                    singletonBeanFactory.put(beanId, instance);
                }
                //根据beanid获取实例对象容器中的对象
                object = singletonBeanFactory.get(beanId);
            }
            //如果socpe等于prototype,则创建并返回多例对象
            if ("prototype".equals(scope)) {
                object = cls.newInstance();
            }
            setFieldValues(beanId, element, scope, cls, object);
            return object;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //暂不支持其他类型,若不是以上两种类型或者遭遇异常则返回null;
        return null;
    }

    /**
     * 此为重载方法,再根据传入的bean的id值获取容器中的对象的同时,还可以自动转换类型。
     * 返回指定的类型,在调用该方法时省略强转的步骤,传入时第二个参数为指定的类型,
     * 方法实现上一个方法,只是在返回对象前加了类型强转
     *
     * @param beanId 传入bean的id
     * @param cls    指定的类型
     * @param <T>    未知类型
     * @return 返回指定的类型
     */
    public <T> T getBean(String beanId, Class<?> cls) {
        return (T) getBean(beanId);
    }

在以上getBean方法中,调用了setFieldValues方法,该方法用于给对象的成员属性赋值

 /**
     * 该方法用于为对象设置成员属性值
     * @param beanId    bean元素的id
     * @param element   bean所对应的element对象
     * @param beanScope bean元素的scope属性
     * @param cls       类对象
     * @param object    要为其成员属性赋值的实例对象
     */
    private void setFieldValues(String beanId, Element element, String beanScope, Class<?> cls, Object object) {
        try {
            //获取每个bean元素下的所有property元素,该元素用于给属性赋值
            List<Element> propEles = element.elements("property");
            //如果property元素集合为null,调用putInMap方法将对象放进Map中
            if (propEles == null) {
                return;
            }
            //遍历property元素集合
            for (Element e : propEles) {
                //获取每个元素的name属性值和value属性值
                String filedName = e.attributeValue("name");
                String filedValue = e.attributeValue("value");
                //利用反射根据name属性获得类的成员属性
                Field field = cls.getDeclaredField(filedName);
                //将该属性设置为可访问(防止成员属性被私有化导致访问失败)
                field.setAccessible(true);
                //获取成员属性的类型名称,若非字符串类型,则需要做相应的转换
                String filedTypeName = field.getType().getName();
                //判断该成员属性是否是int或INteger类型
                if ("int".equals(filedTypeName) || "java.lang.Integer".equals(filedTypeName)){
                    //转换为int类型并为该成员属性赋值
                    int intFiledValue = Integer.parseInt(filedValue);
                    field.set(object,intFiledValue);
                }
                //判断改成员是否是String类型
                if ("java.lang.String".equals(filedTypeName)){
                    //为该成员属性赋值
                    field.set(object, filedValue);
                }
                //此处省略其他类型的判断   道理同上。
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

以上是获取单例对象或多例对象需要调用getBean方法的全部内容,调用完后,还需要关闭容器释放资源,还需要一个destroy方法

 public void destroy(){
        singletonBeanFactory.clear();
        singletonBeanFactory=null;

        beanDefinationFactory.clear();
        beanDefinationFactory=null;

        beanEleMap.clear();
        beanEleMap=null;

        beanScopeMap.clear();
        beanScopeMap=null;
    }

至此,ClassPathXmlApplicationContext类中的内容全部完成,可以写测试类开始测试

package spring.ioc;

import com.spring.ioc.ioc_not_annotation.pojo.User;
import com.spring.ioc.ioc_not_annotation.applicationContext.ClassPathXmlApplicationContext;

public class IOCNotAnnotationTest {
    public static void main(String[] args) {
      
        ClassPathXmlApplicationContext ctx =
                new ClassPathXmlApplicationContext("spring/ioc_not_annottation/user.xml");
  
        //使用手动强转的方式获取单例的user对象
        User user1_1 = (User) ctx.getBean("user1");
        System.out.println("单例user1_1:"+user1_1);
        /**
         *getBean有一个重载方法,根据传入的id值获取到bean的同时,强制转换为指定为类型.
         */
        //使用传入类对象的方式获取单例的user对象
        User user1_2 = ctx.getBean("user1",User.class);
        System.out.println("单例user1_2:"+user1_2);

        //使用手动强转的方式获取多例的user对象
        User user2_1 = (User) ctx.getBean("user2");
        System.out.println("多例user2_1:"+user2_1);

        //使用传入类对象的方式获取单例的user对象
        User user2_2 = ctx.getBean("user2",User.class);
        System.out.println("多例user2_2:"+user2_2);

        ctx.destroy();
    }
}

测试结果

先把实体类中的toString方法注释掉,查看一遍结果

   /* @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }*/

在这里插入图片描述
从控制台输出的结果中可以看到,获取到了4个对象,其中前两个为单例对象,后两个为多例对象,两个单例对象在默认调用Object类中的toString方法是其地址值的hashCode十六进制的映射,其映射值完全一致,可以说明是同一个对象。而且创建了4个对象,其无参的构造方法只执行了三次。

再加上toString方法测试一遍
在这里插入图片描述
可以看到对象所定义的属性值也在创建时成功赋值了。

以上就是基于xml文件动态创建对象的过程。

总结一下流程:
先开始创建工厂,初始化内部的容器,他会通过传入的xml文件路径,找到这个路径并解析他,获取到bean的id、class实体类路径和scope作用域值。再将scope值、通过class路径利用反射机制生成反射对象,分别将他们作为value,beanId作为key存储在类对象容器,和scope容器中,再将beanID作为key,bean节点作为value放入保存节点的map容器中。都存储完毕后,容器初始化完成。
容器初始化完成后,可以调用工厂内的getBean(beanID)方法,他会通过BeanId分别从上述的容器中获取相应的属性,scope,类对象和节点,获取过后,
先判断scope对象是否是单例,如果是则再判断单例容器是否有该对象的实例,如果没有则创建一个类对象,在将beanid作为可以,对象作为值存储在单例容器中,在通过beanid获取到该容器的对象并返回出去,
如果scope对象是多例,则直接创建对象返回出去。

在创建对象期间会先通过beanId获取到bean容器内的bean节点,然后遍历该节点,获取子节点property的name和value,再根据反射用name获取到类对象的成员属性,将这些成员属性设置为可访问(因为类里成员属性设置的是私有属性,为了防止访问私有属性报错,将他设置为可访问),
再将获取到的成员属性进行类型转换,将他们的属性转换成统一属性,比如int和Integer都变成Integer,转换类型完成后,就可以将value3(这个value为property节点的value值)赋值给成员属性了。至此成员属性赋值完毕

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值