Spring IOC精简手写实现

  • Post author:
  • Post category:其他



本文目录


一、这是前言


二、核心代码


三、代码测试


四、总结



一、这是前言

要想实现 Spring IOC 的核心功能,只要明白两个核心知识点即可:

XML 的解析



Java 反射

。这两点同时也是 Java 进阶的重点技术。在学习 Spring 框架的源码时,可以在不断地手写实现中复习 Java 的核心技术。

实现 Spring IOC 精简版的功能可以参照如下思路:

  1. 根据自己的开发需求,将需要创建的对象(bean)配置在 xml 文件中;

  2. 将 xml 文件读取出来,获取创建 bean 对象所需的相关信息;

  3. 利用 Java 反射机制,动态创建 bean 对象,并根据 xml 配置来给对象赋初始值;

  4. 将创建出来的 bean 对象存到 String – Object 键值对的 Map 集合中,这里的 Map 集合就是 IOC 容器;

  5. 当需要使用 bean 对象时,只需要通过 bean 的 id 即可在 IOC 容器中通过 getBean 方法获取到。

二、核心代码

  1. 实体类的创建,包含成员变量的 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 构造器的实现根据步骤抽取封装
  • 基于注解实现

以上,若有错误,还请指正。欢迎评论与转发。



版权声明:本文为TFonion原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。