本文带着大家写一个简单的Spring容器,通过读取beans.xml配置文件,获取第一个JavaBean:Monster的对象,并给对象赋值,放入到容器中,输出对象信息。
先回顾一下使用Spring时,我们是通过ClassPathXmlApplicationContext
得到ioc
容器,容器自动读取beans.xml配置文件,我们通过容器的getBean
方法获取到对象,并输出该对象。
现在要做的就是:
- 自己实现一个ioc容器,前提是自己实现一个属于自己的
ClassPathXmlApplicationContext()
; ClassPathXmlApplicationContext()
中要提供一个getBean()方法。
思路分析@
这里其实重点分析ClassPathXmlApplicationContext()
的具体实现过程:
- 定义一个
ioc
的ConcurrentHashMap
,存放对象名id和对象的映射。 - 创建一个构造器,将下面的操作都放在这个构造器里
- 使用
xml
解析技术-dom4j
技术,将beans.xml
文件中的信息读取出来,并获取到节点信息的属性值 - 使用反射创建对象
- 将对象放入到
ioc
中 - 提供一个
getBean
方法
在通过 xml解析技术-dom4j
技术,将beans.xml文件中的信息读取出来,并获取到节点信息的属性值时,需要先获取到beans.xml文件所在路径 。这里的路径是指 项目 编译后的文件所在路径,也就是target
工作目录下的路径,我们的beans.xml文件是放在resource
目录下的,经过编译后就会跑到target/classes目录下,所以在这里我们通过dom4j
读取的目录应该是D:/Java/JavaProjects/spring-context-v1/target/classes/
;完整的 文件工作路径应该是D:/Java/JavaProjects/spring-context-v1/target/classes/beans.xml
;
String path = this.getClass().getResource("/").getPath(); // System.out.println("path="+path); SAXReader saxReader = new SAXReader(); Document document = saxReader.read(new File(path + iocBeanXmlFile));
在获取到document
文档对象以后,我们就拿到了beans.xml里的所有内容,这个时候,我们获取到根节点,再通过根节点获取到bean标签的第一个bean对象,bean标签内的属性极其属性值都能获取到。通过bean标签的class属性可以获取到全类路径,拿到类路径就可以通过反射创建对象,这个对象的类型就是你类路径的对象。
SAXReader saxReader = new SAXReader(); Document document = saxReader.read(new File(path + iocBeanXmlFile)); Element rootElement = document.getRootElement(); Element bean = (Element) rootElement.elements("bean").get(0); String id = bean.attributeValue("id"); String classFullPath = bean.attributeValue("class"); System.out.println("id="+id); System.out.println("classFullPath="+classFullPath); List<Element> property = bean.elements("property"); String monsterId = property.get(0).attributeValue("value"); String name = property.get(1).attributeValue("value"); System.out.println("monsterId="+monsterId); System.out.println("name="+name);
通过反射创建的对象才能和Monster对象有相似的结构,我们可以把beans.xml中取出来的bean对象的属性值注入到我们反射创建的对象中,再把这个对象加入到ioc集合中。
Class<?> aClass = Class.forName(classFullPath); Monster o = (Monster) aClass.newInstance(); o.setId(Integer.valueOf(monsterId)); o.setName(name); ioc.put(id,o);
最后提供一个getBean
方法,给出 monsterId的时候,能从ioc容器中找到对应映射的Monster对象。
public Object getBean(String id){ return ioc.get(id); }
最后就实现了上图,我们只需要在xml文件中配置bean对象,再把xml文件的名字交给LingHuApplication
容器进行初始化就可以得到一个ioc容器,ioc容器的本质就是一个ConcurrentHashMap
。内部是通过dom4j去读取了bean对象的配置信息,通过这些信息如classFullPath反射创建对象,在往这个新对象中注入读取到bean对象的配置信息。这个新对象被我们放入到ConcurrentHashMap
中了,我们通过getBean(String monsterId)
方法去遍历ConcurrentHashMap
找到新对象。
做这些事情的意义就在于,我们将配置工作和业务工作进行了分离,繁琐的对象管理工作一律通过配置文件来完成,降低了代码的耦合性,剥离了业务代码。所以Spring的容器思想的本质就是:ConcurrentHashMap
+反射创建对象。
完整代码&:
LingHuApplication.java:
package com.linghu.spring.linghuapplication; import com.linghu.spring.bean.Monster; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.context.annotation.ComponentScan; import java.io.File; import java.net.MalformedURLException; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** * @author * @version 1.0 * * 1. 这个程序用于实现Spring的一个简单容器机制 * * 2. 后面我们还会详细的实现 * * 3. 这里我们实现如何将beans.xml文件进行解析,并生成对象,放入容器中 * * 4. 提供一个方法 getBean(id) 返回对应的对象 * * 5. 这里就是一个开胃小点心, 理解Spring容器的机制 */ public class LingHuApplication { private ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>(); public LingHuApplication(String iocBeanXmlFile) throws MalformedURLException, DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException { String path = this.getClass().getResource("/").getPath(); // System.out.println("path="+path); //得到一个解析器 SAXReader saxReader = new SAXReader(); //老师的代码技巧->debug 看看document对象的属性 //分析了document对象的底层结构 Document document = saxReader.read(new File(path+iocBeanXmlFile)); //1. 得到rootElement, 你是OOP Element rootElement = document.getRootElement(); //2. 得到第一个bean-monster01 Element bean = (Element) rootElement.elements("bean").get(0); //获取到第一个bean-monster01的相关属性 String id = bean.attributeValue("id"); String classFullPath = bean.attributeValue("class"); List<Element> property = bean.elements("property"); System.out.println("id="+id); System.out.println("classFullPath="+classFullPath); //获取bean对象中的property属性值-id和name String name = property.get(1).attributeValue("value"); String monsterId = property.get(0).attributeValue("value"); System.out.println("name="+name); System.out.println("id="+monsterId); //使用反射创建对象 Class<?> aClass = Class.forName(classFullPath); //o对象就是monster对象 Monster o = (Monster) aClass.newInstance(); // System.out.println("o="+o); o.setId(Integer.valueOf(monsterId)); o.setName(name); //将创建好的对象放入到ioc对象中 ioc.put(id,o); } //提供一个getBean(id)返回对应的bean对象 public Object getBean(String id){ return ioc.get(id); } }