Spring IOC 底层原理
如何通过 IOC 容器来创建对象:
- 创建 Maven 工程,在 pom.xml 中添加 Spring 框架相关的依赖;
- 新建实体类;
- 在 resources 目录下创建配置文件,可以自定义文件名,比如:spring-ioc.xml;
- 在 spring.xml 中配置 bean 标签,IOC 容器通过加载 bean 标签来创建对象;
- 调用 API 获取 IOC 创建的对象;
创建 Student 实体类
package com.trainingl.entity;
public class Student {
private Integer id;
private String name;
private Double score;
public Student(){
}
public Student(Integer id, String name, Double score) {
this.id = id;
this.name = name;
this.score = score;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
spring.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
">
<bean id="student" class="com.trainingl.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="小明"></property>
<property name="score" value="90"></property>
</bean>
<bean id="student1" class="com.trainingl.entity.Student">
<property name="id" value="2"></property>
<property name="name" value="小张"></property>
<property name="score" value="95"></property>
</bean>
</beans>
获取 IOC 创建的对象
package com.trainingl.test;
import com.trainingl.entity.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//1.加载spring.xml配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.通过运行时类来获取对象
Student stu = (Student)applicationContext.getBean("student");
System.out.println(stu);
}
}
以上是 Spring 通过 IoC 容器创建和获取对象的步骤,之前也介绍过 SpringIoC 的底层实现原理,即:先通过 XML 解析来加载 spring.xml 配置文件,然后使用反射机制调用无参构造函数动态创建对象,并调用 setter 方法完成对象属性的赋值,最后将创建好的对象放在一个类似于 HashMap 的容器里,调用 getBean 方法获取对象时,相当于 map.get(id)
返回一个对象。
很多初学者可能对以上底层的描述也似懂非懂,Spring 底层如何实现、具体使用了哪些技术也全然不知。现在通过 XML解析和反射机制,剖析实现 Spring IoC 的底层处理机制。
第一步:创建 ApplicationContext 接口
package com.trainingl.ioc;
public interface ApplicationContext {
public Object getBean(String id);
}
第二步:创建 ApplicationContext 接口的实现类 ClassPathXmlApplicationContext
Spring IoC容器的底层实现分两步走,用到的技术如下:
- XML 解析:读取 spring.xml,获取
<bean>
标签的配置信息(id,全类名,属性名,属性值等); - 反射机制:根据获得的配置信息,通过反射动态地创建对象;
package com.trainingl.ioc;
import com.trainingl.entity.Bean;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map<String,Object> ioc = new HashMap();
//传入文件路径,构建IOC容器
public ClassPathXmlApplicationContext(String xmlPath){
try {
//1.通过xml解析读取spring.xml中的配置信息
List<Bean> beans = parseXML(xmlPath);
//2.根据这些配置信息通过反射机制创建对象
createObject(beans);
} catch (DocumentException e) {
e.printStackTrace();
}
}
public Object getBean(String id) {
return ioc.get(id);
}
}
定义类成员方法,解析 xml 文件读取 spring.xml 中的配置信息,并封装到实体类里面
package com.trainingl.entity;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class Bean {
private String id;
private String classPath;
private List<Map<String,String>> property;
}
private List<Bean> parseXML(String path) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("src/main/resources/"+path);
//获取根节点
Element rootElement = document.getRootElement(); //获取到<beans>
//遍历根节点
Iterator<Element> iterator1 = rootElement.elementIterator();
List<Bean> beans = new ArrayList<Bean>();
while (iterator1.hasNext()){
Bean bean = new Bean();
Element node = iterator1.next(); //获取到<bean>
String id = node.attributeValue("id");
bean.setId(id);
String aClass = node.attributeValue("class");
bean.setClassPath(aClass);
//迭代属性
Iterator<Element> iterator2 = node.elementIterator();
List<Map<String,String>> propertyList = new ArrayList<Map<String, String>>();
while (iterator2.hasNext()){
Element property = iterator2.next();
Map<String,String> map = new HashMap<String, String>();
String name = property.attributeValue("name");
String value = property.attributeValue("value");
map.put("name",name);
map.put("value",value);
propertyList.add(map);
}
bean.setProperty(propertyList);
beans.add(bean);
}
return beans;
}
根据 XML 实体类的配置信息,通过反射机制创建对象,并完成对象的属性赋值操作
public void createObject(List<Bean> beans){
try{
//通过反射机制创建对象
Iterator<Bean> iterator = beans.iterator();
while (iterator.hasNext()){
Bean bean = iterator.next();
//获取创建对象的全类名
String className = bean.getClassPath();
Class clazz = Class.forName(className);
Object obj = clazz.getConstructor(null).newInstance(null);
//遍历对象的属性值,调用set方法赋值
List<Map<String,String>> properties = bean.getProperty();
int i = 1;
for (Map<String,String> property:properties) {
//调用set方法完成属性赋值操作
setProperty(obj,property,clazz);
}
ioc.put(bean.getId(),obj);
}
}catch (ClassNotFoundException e){
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public void setProperty(Object object,Map<String,String> map,Class clazz){
try {
String name = map.get("name");
String value = map.get("value");
//获取方法名
String methodName = "set" + name.substring(0,1).toUpperCase() + name.substring(1);
//通过反射获取方法
Field declaredField = clazz.getDeclaredField(name);
Method method = clazz.getMethod(methodName, declaredField.getType());
//值的类型转换
Object val = null;
String s = declaredField.getType().getName();
if ("java.lang.Integer".equals(s)) {
val = Integer.parseInt(value);
} else if ("java.lang.Double".equals(s)) {
val = Double.parseDouble(value);
} else if ("java.lang.String".equals(s)) {
val = value;
}
//调用setter赋值
method.invoke(object,val);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
第三步:编写测试类 Test,测试 Spring IoC 底层实现类是否可用;
package com.trainingl.test;
import com.trainingl.entity.Student;
import com.trainingl.ioc.ApplicationContext;
import com.trainingl.ioc.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student)applicationContext.getBean("student");
System.out.println(student);
}
}
控制台的打印结果如下:
至此,Spring IoC底层代码实现解读完毕。现在才发现自己写一个框架真的很复杂,尤其是涉及到 JavaSE 一些关键技术,都必须牢固掌握(集合框架、XML解析、反射机制等)。