•前面的课程主要学习了spring IOC的相关理论和如何使用,spring作为一款优秀的框架,其编程思想也是非常值得我们去学习的。我们在学习编程的道路上,有目标的去学习,才能是我们更轻松,知识掌握更牢记。
•带着输出的目标去学习,会让我们有方向感,学习也更具有针对性,今天,我们一起来手写一个功能与spring IOC类似的框架。仿照源码自己动手写框架并不是要求我们实现框架的全部功能,主要目的是通过自己动手写代码搞清楚框架的核心机制,从而更好的应用框架。
•首先呢,我们一起来理一下思路,如何去写这个框架:
•(1)我们需要根据要求编写xml文件,配置需要创建的POJO类
•(2)其次我们需要编写程序来读取xml文件,获取POJO的相关信息,比如它是通过哪个类创建的?有哪些属性?它们的属性值是什么?
•(3)我们根据第二部获得的信息,结合反射机制动态创建对象,同时完成属性的赋值
•(4)我们还需要创建一个Map集合,该集合用于存储我们创建好的对象,之所以选择Map集合,是因为它是key-value结构,key我们用于存储xml文件中设置的id值,value可以用于存储我们创建的对象。
•(5)最后我们还需要一个测试类,通过加载我们自己创建的IOC容器,用id获取我们需要的对象。
•下面我们一起来动手实践一下:
(1)我们先创建一个POJO类,这个类用于我们最后测试的时候使用
//User.java
public class User {
private int id;
private String name;
//如果我们需要在xml文件中使用property标签给对象属性赋值,则一定在这里给出每个属性的setter方法
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
//这个toString方法是用于最后结果展示,能够显示该对象的每个属性值
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
(2)我们创建一个配置文件,这个配置文件一定要放在resources目录下,名字可以随意起,但后边内容要与之对应,后缀为.xml文件
<!--myIOC.xml-->
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="user" class="pojo.User">
<property name="id" value="1"></property>
<property name="name" value="jacob"></property>
</bean>
</beans>
(3)这一步是我们写这个框架的核心部分,用于读取xml文件,获取信息,并创建对象
首先我们创建一个接口,名字我参考的是spring官方的名字
//ApplicationContext.java,它是一个接口
public interface ApplicationContext {
public Object getBean(String id);
}
接着我们创建它的实现类,名字同样参考spring官方的名字
//ClassPathXmlApplicationContext.java
//这一部分我将导的包也截了下来,之前的我都没截,不知道你们注意了没有
//下边核心代码块的每一部分我都给了注释,不理解的小伙伴可以下方留言喔
//在这个核心代码块中我们用到了dom4j,它是专门用于读取xml文件的,因此我们需要在我们的项目中导入它的依赖jar包,如果你用的是maven工程,下方我会给出它的依赖关系,如果没有用maven也没关系,可以手动下载jar包导入项目中。
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
//该类继承Application接口,名称是参考spring官方的名称,在这里需要用到dom4j,因此需要导入它的依赖jar包,dom4j是专门用于解析xml文件的
public class ClassPathXmlApplicationContext implements ApplicationContext{
private Map<String,Object> ioc=new HashMap<String, Object>();//创建一个Map集合专门用来存储生成的对象
public ClassPathXmlApplicationContext(String path){//构造函数,在测试类创建对象的时候它就已经开始执行了
try{
SAXReader reader=new SAXReader();
Document document=reader.read("./src/main/resources/"+path);//通过所给的路径去寻找xml文件,document代表整个xml文件
Element root=document.getRootElement();//获取根节点,任何xml解析都是从根节点开始的
Iterator<Element> iterator=root.elementIterator();//获取到跟节点接着使用elementIterator获取它的子节点,Iterator是迭代器,迭代遍历根节点的每一个子节点
while(iterator.hasNext()){
Element element=iterator.next();//获取每一个子节点的对象,将每一个子节点对象转化为Element类型,在上边我们运用了泛型<Element>,因此这里不用进行强转
String id=element.attributeValue("id");//获取子节点属性id名称
String className=element.attributeValue("class");//获取子节点属性class名称
Class clazz=Class.forName(className);//运用反射通过获取的class名称获得它的运行时类
Constructor constructor=clazz.getConstructor();//通过运行时类获得该类的无参构造函数
Object object=constructor.newInstance();//通过获得的构造函数创建对象
Iterator<Element> beanIter=element.elementIterator();//需要创建的对象已经完成了,但是该对象是一个空对象,我们要对它进行赋值,继续遍历子节点获得property内容
while(beanIter.hasNext()){
Element property=beanIter.next();//遍历每一个property
String name=property.attributeValue("name");//获得property标签的name
String valueStr=property.attributeValue("value");//获得property标签的value
String methodName="set"+name.substring(0,1).toUpperCase()+name.substring(1);//生成一个字符串,比如property标签的name为“user”,那么生成的字符串就是User,相当于统一了格式
Field field=clazz.getDeclaredField(name);//它反映此Class对象所表示的类或接口的指定已声明方法
Method method=clazz.getDeclaredMethod(methodName,field.getType());//通过反射机制获得目标setter方法
//接下来还有个问题,调用setter完成赋值时所传入参数的数据类型必须和方法定义的参数数据类型一致,但是现在我们获取到的value全部为String类型,
//这就需要做一个映射,根据当前属性的数据类型,对value进行数据类型转换,保证二者类型一致
Object value=null;
if(field.getType().getName()=="long"){
value=Long.parseLong(valueStr);
}
if(field.getType().getName()=="java.lang.String"){
value=valueStr;
}
if(field.getType().getName()=="int"){
value=Integer.parseInt(valueStr);
}
//在这里,我们只列举了long,String,int类型的映射,其他数据类型的转换和这个基本差不多
method.invoke(object,value);
ioc.put(id,object);//将生成的对象放入Map集合中,id值正是我们在xml中配置的id值,此后我们也是通过id来获取该对象的
}
}
}catch(DocumentException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(InstantiationException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}catch(InvocationTargetException e){
e.printStackTrace();
}catch(NoSuchFieldException e){
e.printStackTrace();
}
}
public Object getBean(String id) {
return ioc.get(id);
}
}
!-----------------------------分割线--------------------------!
这一部分是maven工程dom4j依赖
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
!-----------------------------分割线--------------------------!
(4)最后,我们创建一个Test类,用于测试我们写的框架能否正常使用
//Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("myIOC.xml");
User user=(User)applicationContext.getBean("user");
System.out.println(user);
}
}
运行结果:
•我们看到结果正是我们想要创建的对象,属性也是我们提前设定好的属性值。
•总结:
•spring IOC是spring框架中重要的一部分,通过五节的学习总结,让我收获满满,我发现其实框架只是我们使用的一个工具,它并没有我们想象的那么难。之前我一直处于如何使用框架的基础上,对于它的内部是如何实现的,我却一无所知,因此当遇到一些错误时,我有时候就会手无举措,当然,如果我们理解了框架的实现原理,那么对于今后的使用,我相信会轻松许多。
•我理解的spring IOC实现原理是,在测试类中,程序会读取我们给定的xml配置文件,根据配置文件中bean的内容进行对象的创建,有时候也有对象属性的赋值,最后存放在所谓的IOC容器中,我们会通过id值取出我们想要的对象。