资讯专栏INFORMATION COLUMN

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

monw3c / 2382人阅读

Spring作为Java Web最为流行的框架之一,其功能之强大,封装细节之全面不用过多赘述。使用Spring的方式很简单,不需要关注细节,把对象的创建和对象之间的关系都交给框架来管理,仅仅做好配置文件和实现具体的业务逻辑即可。可以说Spring为我们在编写Java Web应用时省去了大量重复的代码,并且可以降低对象与对象之间的耦合度。但若只是知其然,而不知其所以然,在编程时也难免会遇到各种问题,个人的水平也难以有所长进。

因此这篇文章的目的是分享本人对于SpringIOC如何实现控制反转,以及如何在运行过程中动态创建对象的理解,算是在漫长的学习过程中的一个小小的标的。废话不多说,直接上干货!

在手写Spring容器之前,需要做一些前期的准备工作:

首先是创建项目,在这里我为了后期下载jar包方便,创建的是maven工程,是在JDK1.7的环境下。当然你也可以创建普通的Java工程,在需要使用第三方的jar包时手动导入。

在pom.xml文件中添加jar包依赖路径,下载所需要的第三方API,本次需要使用dom4j去解析xml配置文件。

  
      
      dom4j  
      dom4j  
      1.6.1  
      
  

创建xml配置文件,为了后面使用方便,在这里我起名为:user.xml,放在根目录下:

配置文件内容如下:

  
   
      
          
          
          
      
      
          
          
          
      

然后根据xml配置文件中的class路径创建对应的POJO实体类:User

User类中内容如下(为了节省篇幅,省略setter和getter方法):

public class User {  
    private Integer id;  
    private String name;  
    private String password;  
    public User() {  
        System.out.println("无参构造方法执行");  
    }  
    //setters和getters...  
}

主角登场...

创建ClassPathXmlApplicationContext类:

先定义几个后面要用到的容器,这里我使用的是Map来存储对象:

package applicationContext;  

public class ClassPathXmlApplicationContext {  
    /**存储单例对象容器*/  
    private Map singletonBeanFactory;  
    /**存储创建类定义对象的容器*/  
    private Map> beanDefinationFactory;  
    /**存储beanElement对象容器*/  
    private Map beanEleMap;  
    /**存储bean的scope属性容器*/  
    private Map beanScopeMap;  
}  

定义有参的构造方法,在构造方法中初始化容器,并调用初始化方法:

/**有参的构造方法,在创建此类实例时需要指定xml文件路径*/  
public ClassPathXmlApplicationContext(String xmlPath) {  
    //初始化容器  
    singletonBeanFactory = new ConcurrentHashMap();  
    beanDefinationFactory = new ConcurrentHashMap>();  
    beanEleMap = new ConcurrentHashMap();  
    beanScopeMap = new ConcurrentHashMap();  
    //调用初始化方法  
    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元素,elements方法会返回元素的集合  
        List beanElements = rootElement.elements("bean");  
        //遍历元素集合  
        for (Element beanEle : beanElements) {  
            //获取bean的id值,该值用于作为key存储于Map集合中  
            String beanId = beanEle.attributeValue("id");  
            //将beanElement对象存入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");  
            //利用反射技术根据获得的beanClass路径得到类定义对象  
            Class cls = Class.forName(beanClassPath);  
            //如果反射获取的类定义对象不为null,则放入工厂中方便创建其实例对象  
            if(cls!=null){  
                beanDefinationFactory.put(beanId, cls);  
            }  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}

以上为创建ClassPathXmlApplicationContext对象时自动启用的初始化方法,要想获取对象则需要使用getBean方法,代码如下:

/** 
 * 根据传入的bean的id值获取容器中的对象,类型为Object 
 */  
public Object getBean(String beanId){  
    //根据传入beanId获取类对象  
    Class cls = beanDefinationFactory.get(beanId);  
    //根据id获取该bean对象的element元素对象  
    Element beanEle = beanEleMap.get(beanId);  
    //获取存在map中的bean元素的scope属性值  
    String scope = beanScopeMap.get(beanId);  
    Object obj = null;  
    try {  
        //如果scope等于singleton,创建单例对象  
        if("singleton".equals(scope) || null == scope){  
            //判断容器中是否已有该对象的实例,如果没有,创建一个实例对象放到容器中  
            if(singletonBeanFactory.get(beanId)==null){  
                Object instance = cls.newInstance();  
                singletonBeanFactory.put(beanId,instance);  
            }  
            //根据beanId获取对象  
            obj = singletonBeanFactory.get(beanId);  
        }  
        //如果scope等于prototype,则创建并返回多例对象  
        if("prototype".equals(scope)){  
            obj = cls.newInstance();  
        }  
        setFieldValues(beanId, beanEle, scope, cls, obj);  
        return obj;  
    } catch (InstantiationException e) {  
        e.printStackTrace();  
    } catch (IllegalAccessException e) {  
        e.printStackTrace();  
    }  
    //暂不支持其它类型,若不是以上两种类型或遭遇异常,返回null  
    return null;  
}  
/** 
 * 此为重载方法,在根据传入的bean的id值获取容器中的对象的同时,还可以自动转换类型, 
 * 返回指定的类型,在调用该方法时省去强转的步骤,传入时第二个参数为指定的类型, 
 * 方法实现同上一个方法,只是在返回对象前加了类型强转 
 */  
public T getBean(String beanId,Class c){  
    return (T)getBean(beanId);  
      
}

在以上的getBean方法中,调用了setFieldValues方法,该方法代码如下:

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

以上是获取单例或多例对象时需要调用的getBean方法的全部内容。当调用者使用完容器之后,自然还需要关闭容器释放资源,因此还需要有一个destroy方法:

/** 
 * 销毁方法,用于释放资源 
 */  
public void destroy(){  
    singletonBeanFactory.clear();  
    singletonBeanFactory = null;  
      
    beanDefinationFactory.clear();  
    beanDefinationFactory = null;  
      
    beanEleMap.clear();  
    beanEleMap = null;  
      
    beanScopeMap.clear();  
    beanScopeMap = null;  
}

至此,ClassPathXmlApplicationContext类中的内容全部完成,可以写测试类进行测试:

测试类内容如下,这里我就简单写main方法进行测试:

public class springIocTest {  
    public static void main(String[] args) {  
        //创建ClassPathXmlApplicationContext对象  
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("user.xml");  
        //使用手动强转的方式获取单例的User对象  
        User user1_1 = (User) ctx.getBean("user1");  
        System.out.println("单例user1_1:"+user1_1);  
        //使用传入类对象的方式获取单例的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);  
    }  
} 

控制台打印输出结果:

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

如果在User类型加入toString方法:

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

再次运行程序,控制台输出结果如下:

可以看到对象所定义的属性值也在创建时成功赋值了。

以上是我近期学习Spring所总结的内容,关于创建多例对象的源码其实我也没有找到,目前所写的只是基于我的思路写出来的方案,与大家一起分享。由于个人水平有限,难免会有写错或者遗漏的地方,甚至可能会有以偏概全。但这并不重要,正如开篇所说,这只是我学习Java在编程成长路上的一个小小的标的。如果有大牛看到,欢迎留言指正,不胜感激~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68377.html

相关文章

  • 手写SpringDI依赖注入

    摘要:如感兴趣,可移步手写之基于动态创建对象手写之基于注解动态创建对象今天将详细介绍如何手写依赖注入,在运行过程中如何动态地为对象的属性赋值。完成后在中会有相关的包出现进行注入前需要创建工厂,在运行时从工厂中取出对象为属性赋值。 前两篇文章介绍了关于手写Spring IOC控制反转,由Spring工厂在运行过程中动态地创建对象的两种方式。如感兴趣,可移步: 手写Spring之IOC基于xml...

    Cruise_Chan 评论0 收藏0
  • 手写SpringIOC基于注解动态创建对象

    摘要:上一篇博客介绍了如何基于配置文件在运行时创建实例对象,这篇博客将介绍基于注解方式怎样实现对象的创建。方便测试,该类型分别创建两个单例和多例的类型。注意这种为对象注入属性值的方式耦合度较高,可根据情况使用。 上一篇博客介绍了如何基于xml配置文件在运行时创建实例对象,这篇博客将介绍基于注解方式怎样实现对象的创建。 废话不多说,直接上代码。 首先还是创建项目,由于这次不需要使用第三方的AP...

    Andrman 评论0 收藏0
  • Spring入门IOC和AOP学习笔记

    摘要:入门和学习笔记概述框架的核心有两个容器作为超级大工厂,负责管理创建所有的对象,这些对象被称为。中的一些术语切面切面组织多个,放在切面中定义。 Spring入门IOC和AOP学习笔记 概述 Spring框架的核心有两个: Spring容器作为超级大工厂,负责管理、创建所有的Java对象,这些Java对象被称为Bean。 Spring容器管理容器中Bean之间的依赖关系,使用一种叫做依赖...

    wenyiweb 评论0 收藏0
  • 明天面试?吓得我赶紧手写了一个Spring

    摘要:你都是如何回答面试官的问题的我不知道,我一般会通过手写一个来加深自己的印象。如今,已然成为了一个生态。运行阶段主要是完成容器启动以后,完成用户请求的内部调度,并返回响应结果。因此,要先写一个针对类名首字母处理的工具方法。 引言 几乎每个面试的程序员都会碰到Spring相关的面试问题,或浅或深。你都是如何回答面试官的问题的?——我不知道,我一般会通过手写一个Spring来加深自己的印象。...

    stefanieliang 评论0 收藏0
  • 仿照 Spring 实现简单的 IOC 和 AOP - 下篇

    摘要:在上文中,我实现了一个很简单的和容器。比如,我们所熟悉的就是在这里将切面逻辑织入相关中的。初始化的工作算是结束了,此时处于就绪状态,等待外部程序的调用。其中动态代理只能代理实现了接口的对象,而动态代理则无此限制。 1. 背景 本文承接上文,来继续说说 IOC 和 AOP 的仿写。在上文中,我实现了一个很简单的 IOC 和 AOP 容器。上文实现的 IOC 和 AOP 功能很单一,且 I...

    AlexTuan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<