目录
8、MyFactory接口的实现类MyClassPathXmlApplicationContext
1、我的上一篇文章
在我的上一篇文章中,我简单的看了Spring官网中对于SpringFramework的介绍,初步使用了SpringFramework,将配置搭了起来,程序可以正常运行,也初步的解释了一些文件的作用和文件以及类之间的关系,下面是我的上一篇文章的地址:
SpringFramework简单的环境搭建_你是我的日月星河的博客-CSDN博客_springframework 环境搭建
2、本篇博客的目的和内容
本篇博客我将会手动模拟实现SpringFramework的SpringIOC的工作原理,也就是实体类Bean对象的实例化。会有代码和代码的讲解
3、简单的思想和需要的POM依赖
先说一下大概的模拟方法:我们就完全按照SpringFramework的思路,对它里面的每一个类或者是接口,文件都尽量的去模拟,按照它的思路去自己写代码(毕竟是初学,就不要另辟蹊径了)。
在SpringFramework中,每个bean标签都有一个id属性和class属性,当我们通过ApplicationContext对象的getBean方法,以XML配置文件中bean标签的id属性值作为参数的时候,可以直接就得到了一个实例化好的Bean对象(不需要我们去new了),这很明显,一个id属性值就对应一个实例化好的对象,这明显是Map的结构啊!
以上只是一种想法。这里先说一下,我们手动模拟的过程中,也需要使用到XML文件,只不过不需要我们写XML文件的那些文件头,因为文件头主要规定了能够使用那些标签,标签中有那些属性,以及标签之间的嵌套关系。我们实际编程手动首先的时候,按照万物皆对象的原则,我们可以先创建一个Bean类,这个Bean类专门存储XML文件中bean(这里的bean只是一个名称而已,我们自己写XML文件,可以随便定义,你定义为s标签也行)标签里面的id(这个属性是跟上面的bean标签同理的,名称我们可以任意写,因为是我们自己模拟SpringIOC过程,在写XML文件嘛)属性和class属性的值。
现在还有一个问题,我们写好的XML文件,怎么去读取这个文件呢?
这里我们可以使用现有的jar包,也就是导入如下的两个依赖:
上面就是我们需要使用的POM依赖,可以帮助我们读取XML文件里面的内容。
4、项目的文件结构
5、编写XML文件
下面是XML文件的内容:
由于是我们自己写的XML文件,因此就是我们随便定义的beans和bean标签,里面还有id属性和class属性,再说一下:这些标签名和属性名是可以自己随意定义的,我这样定义只是为了容易理解。
UserDao和UserService都是两个实体类,这两个实体类的代码我就不写了,里面各自简单的写一个公有的公共方法就行啦
6、编写XML文件中bean标签对应的类MyBean
bean标签中,我们定义了id属性和class属性,我们是需要读取XML文件的,将标签中的属性值读取出来,这里我们使用一个实体类来存储,MyBean类如下所示:
7、 拥有getBean方法的接口:MyFactory
我们手动模拟的MyFactory接口就类似于SpringFramework中的ApplicationContext接口或者是BeanFactory接口。代码如下所示:
package com.example.factory;
/*
这个是我们的自定义工厂类
相当于 Spring 的 ApplicationContext接口或者说是它的父接口 BeanFactory
*/
public interface MyFactory {
//通过我们的XML文件中的 id属性值获取我们的实例化对象
public Object getBean(String id);
}
8、MyFactory接口的实现类MyClassPathXmlApplicationContext
MyClassPathXmlApplicationContext类就类似于SpringFramework中的ClassPathXmlApplicationContext类或者是FileSystemXmlApplicationContext类,就是需要真正读取XML文件的类。当然,这个类是需要继承MyFactory接口的。代码如下:
package com.example.factory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import org.dom4j.xpath.DefaultXPath;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//这个是我们对应工厂的接口实现类
//相当于Spring的 ClassPathXmlApplicationContext 类
/*
1、我们要读取我们的配置文件,也就是将我们的配置文件读取出来;
我们通过这个类的构造函数的方法传过来
2、然后我们要解析这个配置文件,得到每一个bean标签的id属性值和class属性值,并设置到对应的MyBean对象中,再存到集合中
3、然后我们要遍历这个集合,得到每一个class属性对应的实例化对象,然后设置到Map中,id值多为key,这个实例化对象作为value
4、我们会通过getBean方法,从Map对象中通过id获取指定的value,value也就是实例化对象
*/
public class MyClassPathXmlApplicationContext implements MyFactory{
//我们首先定义一个 Map对象 用来存放id属性和对应实例化好的 Bean对象
private Map<String,Object> beanMap=new HashMap<>();
//定义List集合,用来存放配置文件中bean标签对应的id与class属性值得 MyBean对象
private List<MyBean> beanList=null;
//1、我们要读取我们的配置文件,也就是将我们的配置文件读取出来;
// 我们通过这个类的构造函数的方法传过来
public MyClassPathXmlApplicationContext(String fileName){
parseXml(fileName);
instanceBean();
}
//3、然后我们要遍历这个集合,得到每一个class属性对应的实例化对象,然后设置到Map中,id值多为key,这个实例化对象作为value
private void instanceBean() {
//我们首先判断我们的 beanList是否为空?
if (beanList!=null&&beanList.size()>0){
//这里我们遍历beanList集合,得到对应的 MyBean对象
for (MyBean myBean : beanList) {
//得到id值
String id = myBean.getId();
//得到class值
String clazz = myBean.getClazz();
//接下来我们要通过反射,实例化指定class属性值对应的Bean对象
try {
//这里多说一下,下面的这条语句更好,是目前推荐的
//第二行代码从 JAVA 9开始已经不推荐了,使用的时候你会看到花了一条线
Object object = Class.forName(clazz).getDeclaredConstructor().newInstance();
// Object object = Class.forName(clazz).newInstance();
//将我们的id值与实例化好的bean对象,设置到map中
beanMap.put(id,object);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
// 2、然后我们要解析这个配置文件,得到每一个bean标签的id属性值和class属性值,并设置到对应的MyBean对象中,再存到集合中
private void parseXml(String fileName) {
try {
//首先得到我们的解析器 是在我们的 dom4j jar包里面的
SAXReader reader=new SAXReader();
//下面我们还要得到配置文件的 URL 这个代码我还不是很理解, 需要查一下
URL url = this.getClass().getClassLoader().getResource(fileName);
//下面我们要解析配置文件,得到一个 Document对象
Document document = reader.read(url);
//这个文档对象我们就需要去解析, 这里我们会使用到 XPath语法
//这里我们可以到菜鸟教程上看一看关于 XPath的介绍,简单的学习一下
//我们肯定要获取 beans标签下的所有的 bean标签
//XPath使用路径表达式来选取 XML文档中的 节点或者是节点集 这个XPath是一个接口
XPath xPath = document.createXPath("beans/bean");
//上面我们是需要这个 文档创建一个 有特殊功能的XPath对象以后,由这个对象按照它的特殊功能操作这个文档
//下面我们通过XPath语法得到对应的bean标签, 会返回一个 Element集合 Element也是dom4j里面的对象
List<Element> elementList=xPath.selectNodes(document);
//我们要先判断一下这个集合是否为 空
if (elementList!=null&&elementList.size()>0){
beanList=new ArrayList<>();//因为我们上面没有实例化
//这里我们遍历Element集合,得到Element对象,得到标签对应的属性值
for (Element element : elementList) {
//首先得到id属性的值 attributeValue:通过指定属性名得到对应的属性值
String id=element.attributeValue("id");
String clazz=element.attributeValue("class");
//下面将我们的 id属性值和class属性值设置到我们的一个 MyBean对象中
MyBean myBean=new MyBean(id,clazz);
beanList.add(myBean);//将这个MyBean对象添加到集合里面
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
// 4、我们会通过getBean方法,从Map对象中通过id获取指定的value,value也就是实例化对象
@Override
public Object getBean(String id) {
//这个就是通过id,从我们的Map对象中获取对应的实例化对象
Object object = beanMap.get(id);
return object;
}
}
这个类的代码比较的长,我里面也有注释,下面我还是解释一下代码:
首先就是一个beanMap属性,这个属性就是存放XML文件中bean标签的id属性值和它对应的实体类的(这里你也许就发现了:我们使用SpringIOC的依赖注入,每一次得到的其实是堆里面的同一个实体类。这一点也说明,某一些实体类是不能交给SpringIOC管理的,比如Student类,不可能每一个Student都是一样的吧!!)
然后是一个MyBean类型的List集合,我们读取XML文件的时候,每一个bean标签都会对应一个MyBean对象,这时候我们就将这些MyBean对象存储在List集合中,使用起来也方便。
再往下,就是我们的一个有参构造方法,这个参数是一个字符串,字符串就是XML文件的文件名,或者说是它的路径,然后下面调用内部的parseXml方法和instanceBean方法,读取XML文件的内容,并且将数据整理出来,存到beanMap属性和MyBean属性中。
parseXML()方法:这个方法就是读取XML配置文件的,将每一个XML文件里面的bean标签都对应生成一个MyBean实体类对象,然后放入List集合中。这个方法中的代码我目前也还不是很理解,只能看我上面的注释了。
instanceBean()方法:我们再parseXML()方法中,已经将XML文件中的id属性值和class属性值读取了出来,组成一个MyBean对象,放在了List集合中。class属性值就是对应实体类的地址,我们需要利用JAVA的反射机制,根据这个地址,实例化出对应的对象。然后将id属性值和实例化出来的这个对象放入Map中,id属性值作为键,实例化出来的对象作为值。对于JAVA的反射机制,具体的实现原理,我现在也不是很理解,后面我懂了,再补充博客。其实反射简单来看就是根据提供的类路径字符串,new出来对象。
getBean()方法:这个方法是我们继承了接口以后,需要实现的方法。这个方法就比较简单了,方法的参数就是一个字符串,根据这个字符串,到Map中寻找对应的已经实例化好的对象,然后返回就可以了。
9、Main方法中进行测试
编写Main方法,测试一下,代码如下:
package com.example;
import com.example.factory.MyClassPathXmlApplicationContext;
import com.example.factory.MyFactory;
import com.example.service.UserService;
public class Starter {
public static void main(String[] args) {
//先得到我们的一个工厂类
MyFactory myFactory=new MyClassPathXmlApplicationContext("spring.xml");
//然后通过myFactory的方法,得到指定的Bean对象
UserService userService = (UserService) myFactory.getBean("userService");
userService.test();
}
}
控制台是可以正确执行test()方法的,并不出现空指针异常的情况。
10、我的下一篇博客
SpringIOC如何加载配置文件_你是我的日月星河的博客-CSDN博客https://blog.csdn.net/weixin_46281472/article/details/124183794