博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
手写Spring之IOC基于xml动态创建对象
阅读量:6277 次
发布时间:2019-06-22

本文共 8634 字,大约阅读时间需要 28 分钟。

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

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

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

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

clipboard.png

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

dom4j
dom4j
1.6.1

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

clipboard.png

配置文件内容如下:

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

clipboard.png

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

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

主角登场...

创建ClassPathXmlApplicationContext类:

clipboard.png

先定义几个后面要用到的容器,这里我使用的是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类中的内容全部完成,可以写测试类进行测试:

clipboard.png

测试类内容如下,这里我就简单写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);      }  }

控制台打印输出结果:

clipboard.png

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

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

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

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

clipboard.png

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

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

转载地址:http://logpa.baihongyu.com/

你可能感兴趣的文章
编译Android内核
查看>>
再谈GC2:Java垃圾收集器与GC日志分析实践
查看>>
IDEA环境下SSM整合------环境配置
查看>>
构建自适应的手机页面
查看>>
YARN的AsyncDispatcher原理
查看>>
[Coursera][From Nand to Tetris / Part I] 第六周 汇编器项目 python 实现
查看>>
阻止了 WannaCry 扩散的研究员承认开发恶意软件
查看>>
云栖大会首设“科技脱贫”专场 ,20张会场门票等你来拿!
查看>>
ZLG 发布开源 GUI 引擎 AWTK
查看>>
一个不可思议的MySQL慢查分析与解决
查看>>
[Cake] 0.C#Make自动化构建-简介
查看>>
《TCP/IP协议》- TCP协议知识目录
查看>>
详尽! Win10安装Java8+Tomcat9!
查看>>
1127
查看>>
一次痛的经历
查看>>
智能运维(AIOps)时代开启,一文帮你快速了解其定义与发展现状
查看>>
第1讲 快速入门 《Kotlin 极简教程 》
查看>>
[Hadoop]MapReducer工作过程
查看>>
VMware PowerCli批量实现虚拟机快照备份
查看>>
小程聊微服务-基于dubbo的mock测试系统
查看>>