本文目录
一、这是前言
要想实现 Spring IOC 的核心功能,只要明白两个核心知识点即可:
XML 的解析
和
Java 反射
。这两点同时也是 Java 进阶的重点技术。在学习 Spring 框架的源码时,可以在不断地手写实现中复习 Java 的核心技术。
实现 Spring IOC 精简版的功能可以参照如下思路:
-
根据自己的开发需求,将需要创建的对象(bean)配置在 xml 文件中;
-
将 xml 文件读取出来,获取创建 bean 对象所需的相关信息;
-
利用 Java 反射机制,动态创建 bean 对象,并根据 xml 配置来给对象赋初始值;
-
将创建出来的 bean 对象存到 String – Object 键值对的 Map 集合中,这里的 Map 集合就是 IOC 容器;
-
当需要使用 bean 对象时,只需要通过 bean 的 id 即可在 IOC 容器中通过 getBean 方法获取到。
二、核心代码
-
实体类的创建,包含成员变量的 getter 、 setter 、 toString 方法,Student.java 如下:
package com.gooyait.ioc;
public class Student {
private String name;
private int age;
public Student() { }
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
2. 编辑 xml 文件,书写格式应该遵循 Spring 配置文件的格式,文件名为 spring.xml ,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="student" class="com.gooyait.ioc.Student">
<property name="name" value="G"/>
<property name="age" value="100"/>
</bean>
</beans>
3. 定义一个 ApplicationContext 接口,再定义一个 getBean 方法,用来通过 bean 的 id 来获取 bean 对象。如下:
package com.gooyait.ioc;
public interface ApplicationContext {
public Object getBean(String id);
}
4. 接下来,自己实现创建好的 ApplicationContext 接口,可参考以下步骤:
- 定义实现类:ClassPathXmlApplicationContext.java ;
package com.gooyait.ioc;
public class ClassPathXmlApplicationContext implements ApplicationContext {
public Object getBean(String id) {
return nulll;
}
}
-
使用键值对的存储结构 Map 集合来创建一个简易的 IOC 容器;
// 简易的 IOC 容器
private Map<String, Object> ioc = new HashMap<String, Object>();
- 在类的构造器中利用 dom4j 解析 xml 文件,再通过 Java 反射创建 bean 对象,保存到 Map 集合中;
// 解析配置文件
@SuppressWarnings({ "unchecked", "rawtypes" })
public ClassPathXmlApplicationContext(String path) throws Exception {
// 第一步:加载Spring.xml,dom4j进行解析
SAXReader reader = new SAXReader();
Document document = reader.read("./config/" + path);
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
// 第二步:while循环内部完成对bean标签的解析,首先 获取bean标签的id值和class值
Element element = iterator.next();
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// 第三步:通过反射机制获取className对应的运行时类,进而获取无参构造器函数创建bean
Class clazz = Class.forName(className);
// 获取无参构造器,创建目标对象
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
// 第四步:为bean对象赋值
Iterator<Element> beanIter = element.elementIterator();
while(beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name");
String valueStr = property.attributeValue("value");
// 先通过name获取到属性对应的setter方法,然后调用setter方法将value作为参数传入来完成赋值
String methodName = "set" + name.substring(0,1 ).toUpperCase() + name.substring(1);
Field field = clazz.getDeclaredField(name);
Method method = clazz.getDeclaredMethod(methodName, field.getType());
// 根据成员 变量的类型,将value进行转换
Object value = null;
String fieldName = field.getType().getName();
if(fieldName == "long"){
value = Long.parseLong(valueStr);
} else if(fieldName == "java.lang.String"){
value = valueStr;
} else if(fieldName == "int"){
value = Integer.parseInt(valueStr);
}
method.invoke(object, value);
// 第五步:存入IOC容器
ioc.put(id, object);
}
}
}
- 在 getBean 方法中实现通过 bean 的 id 从 IOC 容器中获取 bean 对象并返回。
public Object getBean(String id) {
return ioc.get(id);
}
完整的核心代码:
package com.gooyait.ioc;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class ClassPathXmlApplicationContext implements ApplicationContext {
// 简易的 IOC 容器
private Map<String, Object> ioc = new HashMap<String, Object>();
// 解析配置文件
@SuppressWarnings({ "unchecked", "rawtypes" })
public ClassPathXmlApplicationContext(String path) throws Exception {
// 第一步:加载Spring.xml,dom4j进行解析
SAXReader reader = new SAXReader();
Document document = reader.read("./config/" + path);
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
// 第二步:while循环内部完成对bean标签的解析,首先 获取bean标签的id值和class值
Element element = iterator.next();
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// 第三步:通过反射机制获取className对应的运行时类,进而获取无参构造器函数创建bean
Class clazz = Class.forName(className);
// 获取无参构造器,创建目标对象
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
// 第四步:为bean对象赋值
Iterator<Element> beanIter = element.elementIterator();
while(beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name");
String valueStr = property.attributeValue("value");
// 先通过name获取到属性对应的setter方法,然后调用setter方法将value作为参数传入来完成赋值
String methodName = "set" + name.substring(0,1 ).toUpperCase() + name.substring(1);
Field field = clazz.getDeclaredField(name);
Method method = clazz.getDeclaredMethod(methodName, field.getType());
// 根据成员 变量的类型,将value进行转换
Object value = null;
String fieldName = field.getType().getName();
if(fieldName == "long"){
value = Long.parseLong(valueStr);
} else if(fieldName == "java.lang.String"){
value = valueStr;
} else if(fieldName == "int"){
value = Integer.parseInt(valueStr);
}
method.invoke(object, value);
// 第五步:存入IOC容器
ioc.put(id, object);
}
}
}
public Object getBean(String id) {
return ioc.get(id);
}
}
三、代码测试
创建测试类 App.java 包含 main 方法作为程序的主入口,根据 Spring 框架的风格,来初始化加载 spring.xml 文件,然后根据 bean 的 id 获取已经配置好的 bean 对象,如下:
package com.gooyait.ioc;
import org.dom4j.DocumentException;
public class App {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
运行结果:
Student [name=G, age=22]
四、总结
1、以上代码虽然简陋,但已经实现了 Spring IOC 的核心功能,可以很清晰地看到其原理机制。
2、若想要动手继续完善,可参考一下几点:
- 扩展 bean 的成员变量的类型,可以尝试支持自定义类型
- 使 spring.xml 文件书写配置更灵活,如当省略 bean 的 id时,默认为 class 的值对应的类名
- 将 ClassPathXmlApplicationContext 构造器的实现根据步骤抽取封装
- 基于注解实现
以上,若有错误,还请指正。欢迎评论与转发。