Spring IoC 容器的实现原理:工厂模式 + 解析XML + 反射机制
我们给自己的框架起名为:mySpring(我的春天)
一、创建 mySpring 模块
引入dom4j 和 jaxen 的依赖,因为要使用它解析XML文件,还有 junit 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.myspringframework</groupId>
<artifactId>mySpring</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
二、准备要管理的 Bean
准备好我们要管理的Bean(这些Bean在将来开发完框架之后是要删除的)
注意包名,不要用org.myspringframework包,因为这些Bean不是框架内置的。是将来使用我们框架的程序员提供的
package org.qiu.myspring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.myspring.bean
* @date 2022-11-12-11:07
* @since 1.0
*/
public class User {
private String name;
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
package org.qiu.myspring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.myspring.bean
* @date 2022-11-12-11:08
* @since 1.0
*/
public class UserDao {
public void insert(){
System.out.println("mysql数据库正在保存用户数据.......");
}
}
package org.qiu.myspring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.myspring.bean
* @date 2022-11-12-11:09
* @since 1.0
*/
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
三、准备 myspring.xml 配置文件
将来在框架开发完毕之后,这个文件也是要删除的。因为这个配置文件的提供者应该是使用这个框架的程序员
文件名随意,我们这里叫做:myspring.xml
文件放在类路径当中即可,我们这里把文件放到类的根路径下
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这个配置文件是使用 mySpring 框架的开发人员提供的 -->
<beans>
<bean id="user" class="org.qiu.myspring.bean.User">
<property name="name" value="张三"></property>
<property name="age" value="35"></property>
</bean>
<bean id="userDaoBean" class="org.qiu.myspring.bean.UserDao"/>
<bean id="userService" class="org.qiu.myspring.bean.UserService">
<property name="userDao" ref="userDaoBean"/>
</bean>
</beans>
使用value给简单属性赋值。使用ref给非简单属性赋值。
四、编写 ApplicationContext 接口
ApplicationContext 接口中提供一个 getBean() 方法,通过该方法可以获取 Bean 对象。
注意包名:这个接口就是 myspring 框架中的一员了。
package org.myspringframework.core;
/**
* Spring 应用上下文接口
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.myspringframework.core
* @date 2022-11-12-11:22
* @since 1.0
*/
public interface ApplicationContext {
/**
* 根据 beanName 获取 bean 对象
* @param beanName myspring 配置文件中 bean 标签的 id
* @return 对应的单例 bean 对象
*/
Object getBean(String beanName);
}
五、编写 ClassPathXmlApplicationContext
ClassPathXmlApplicationContext 是 ApplicationContext 接口的实现类。该类从类路径当中加载myspring.xml 配置文件
package org.myspringframework.core;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.myspringframework.core
* @date 2022-11-12-11:24
* @since 1.0
*/
public class ClassPathXmlApplicationContext implements ApplicationContext{
/**
* 解析 myspring 配置文件,初始化所有的 bean 对象
* @param configLocation spring 配置文件的路径
* (使用 ClassPathXmlApplicationContext 配置文件应当放到类路径下)
*/
public ClassPathXmlApplicationContext(String configLocation) {
}
@Override
public Object getBean(String beanName) {
return null;
}
}
六、确定采用 Map 集合存储 Bean
确定采用Map集合存储Bean实例
Map集合的key存储beanId,value存储Bean实例。Map<String,Object>
在ClassPathXmlApplicationContext类中添加Map<String,Object>属性
并且在ClassPathXmlApplicationContext类中添加构造方法,该构造方法的参数接收myspring.xml文件
同时实现getBean方法
package org.myspringframework.core;
import java.util.HashMap;
import java.util.Map;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.myspringframework.core
* @date 2022-11-12-11:24
* @since 1.0
*/
public class ClassPathXmlApplicationContext implements ApplicationContext{
private Map<String, Object> singletonObjects = new HashMap<>();
/**
* 解析 myspring 配置文件,初始化所有的 bean 对象
* @param configLocation spring 配置文件的路径
* (使用 ClassPathXmlApplicationContext 配置文件应当放到类路径下)
*/
public ClassPathXmlApplicationContext(String configLocation) {
// 解析 myspring.xml 配置文件,实例化 bean,将 bean 存放到 singletonObjects 集合中
}
/**
* 根据 beanName 在 singletonObjects 集合中获取对应的 bean 对象
* @param beanName myspring 配置文件中 bean 标签的 id
* @return 对应的 bean 对象
*/
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
七、解析配置文件实例化所有 Bean
在ClassPathXmlApplicationContext 的构造方法中解析配置文件,获取所有 bean 的类名,通过反射机制调用无参数构造方法创建 Bean。并且将 Bean 对象存放到 Map 集合中
/**
* 解析 myspring 配置文件,初始化所有的 bean 对象
* @param configLocation spring 配置文件的路径
* (使用 ClassPathXmlApplicationContext 配置文件应当放到类路径下)
*/
public ClassPathXmlApplicationContext(String configLocation) {
try {
// 解析 myspring.xml 配置文件,实例化 bean,将 bean 存放到 singletonObjects 集合中
// dom4j 解析 XML 文件的核心对象
SAXReader reader = new SAXReader();
// 获取一个输入流,指向配置文件
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
// 读文件
Document document = reader.read(in);
// 获取所有的 bean 标签
List<Node> nodes = document.selectNodes("//bean");
// 遍历 bean 标签
nodes.forEach(node -> {
try {
// 向下转型,为了使用 Element 接口中更丰富的方法
Element beanElement = (Element) node;
// 获取 id 属性
String id = beanElement.attributeValue("id");
// 获取 class 属性
String className = beanElement.attributeValue("class");
logger.info("bean id:" + id + ",bean className:" + className);
// 使用反射机制创建对象,并存储到 Map 集合中,提前曝光
Class<?> clazz = Class.forName(className);
Object bean = clazz.newInstance();
singletonObjects.put(id,bean);
// 记录日志
logger.info(singletonObjects.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
八、测试能否获取到 Bean
@Test
public void testMySpring(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
Object user = applicationContext.getBean("user");
Object userDao = applicationContext.getBean("userDaoBean");
Object userService = applicationContext.getBean("userService");
System.out.println(user);
System.out.println(userDao);
System.out.println(userService);
}
运行结果:
九、给 Bean 的属性赋值
通过反射机制调用 set 方法,给 Bean 的属性赋值
package org.myspringframework.core;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.myspringframework.core
* @date 2022-11-12-11:24
* @since 1.0
*/
public class ClassPathXmlApplicationContext implements ApplicationContext{
private static final Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);
private Map<String, Object> singletonObjects = new HashMap<>();
/**
* 解析 myspring 配置文件,初始化所有的 bean 对象
* @param configLocation spring 配置文件的路径
* (使用 ClassPathXmlApplicationContext 配置文件应当放到类路径下)
*/
public ClassPathXmlApplicationContext(String configLocation) {
try {
// 解析 myspring.xml 配置文件,实例化 bean,将 bean 存放到 singletonObjects 集合中
// dom4j 解析 XML 文件的核心对象
SAXReader reader = new SAXReader();
// 获取一个输入流,指向配置文件
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
// 读文件
Document document = reader.read(in);
// 获取所有的 bean 标签
List<Node> nodes = document.selectNodes("//bean");
// 遍历 bean 标签
nodes.forEach(node -> {
try {
// 向下转型,为了使用 Element 接口中更丰富的方法
Element beanElement = (Element) node;
// 获取 id 属性
String id = beanElement.attributeValue("id");
// 获取 class 属性
String className = beanElement.attributeValue("class");
logger.info("bean id:" + id + ",bean className:" + className);
// 使用反射机制创建对象,并存储到 Map 集合中,提前曝光
Class<?> clazz = Class.forName(className);
Object bean = clazz.newInstance();
singletonObjects.put(id,bean);
// 记录日志
logger.info(singletonObjects.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
nodes.forEach(node -> {
try {
Element beanElement = (Element) node;
// 获取 id 属性
String id = beanElement.attributeValue("id");
// 获取 class 属性
String className = beanElement.attributeValue("class");
// 获取 Class
Class<?> clazz = Class.forName(className);
// 获取该 bean 标签下的所有 属性标签 property 标签
for (Element property : beanElement.elements("property")) {
// 获取属性名
String propertyName = property.attributeValue("name");
// 获取属性类型
Field field = clazz.getDeclaredField(propertyName);
logger.info("属性名:" + propertyName);
// 获取 set 方法名
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
// 获取 set 方法
Method setMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
// 获取具体的值
String value = property.attributeValue("value");
String ref = property.attributeValue("ref");
Object realVal = null;
// 值是简单类型
if (value != null) {
/**
* 声明支持的简单类型
* byte short int long float double boolean char
* Byte Short Integer Long Float Double Boolean Character
* String
*/
// 属性类型名
// String propertyTypeSimpleName = field.getType().getName(); 带有包名
String propertyTypeSimpleName = field.getType().getSimpleName();
switch (propertyTypeSimpleName){
case "byte":
realVal = Byte.parseByte(value);
break;
case "short":
realVal = Short.parseShort(value);
break;
case "int":
realVal = Integer.parseInt(value);
break;
case "long":
realVal = Long.parseLong(value);
break;
case "float":
realVal = Float.parseFloat(value);
break;
case "double":
realVal = Double.parseDouble(value);
break;
case "boolean":
realVal = Boolean.parseBoolean(value);
break;
case "char":
realVal = value.charAt(0);
break;
case "Byte":
realVal = Byte.valueOf(value);
break;
case "Short":
realVal = Short.valueOf(value);
break;
case "Integer":
realVal = Integer.valueOf(value);
break;
case "Long":
realVal = Long.valueOf(value);
break;
case "Float":
realVal = Float.valueOf(value);
break;
case "Double":
realVal = Double.valueOf(value);
break;
case "Boolean":
realVal = Boolean.valueOf(value);
break;
case "Character":
realVal = Character.valueOf(value.charAt(0));
break;
case "String":
realVal = value;
break;
}
// 调用 set 方法
setMethod.invoke(singletonObjects.get(id), realVal);
}
// 值是非简单类型
if (ref != null) {
// 调用 set 方法
setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref));
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据 beanName 在 singletonObjects 集合中获取对应的 bean 对象
* @param beanName myspring 配置文件中 bean 标签的 id
* @return 对应的 bean 对象
*/
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
@Test
public void testMySpring(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.save();
}
运行结果:
十、打包发布
将多余的类以及配置文件删除,使用 maven 打包发布
十一、站在程序员角度使用 mySpring 框架
<dependencies>
<dependency>
<groupId>org.myspringframework</groupId>
<artifactId>mySpring</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
package org.qiu.myspring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.myspring.bean
* @date 2022-11-12-14:43
* @since 1.0
*/
public class Vip {
private String name;
private int age;
private double height;
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHeight(double height) {
this.height = height;
}
}
package org.qiu.myspring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.myspring.bean
* @date 2022-11-12-14:43
* @since 1.0
*/
public class OrderDao {
public void insert(){
System.out.println("正在保存订单信息......");
}
}
package org.qiu.myspring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.myspring.bean
* @date 2022-11-12-14:44
* @since 1.0
*/
public class OrderService {
private OrderDao orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void generate(){
orderDao.insert();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="vip" class="org.qiu.myspring.bean.Vip">
<property name="name" value="张三丰"/>
<property name="age" value="120"/>
<property name="height" value="1.82"/>
</bean>
<bean id="orderDao" class="org.qiu.myspring.bean.OrderDao"/>
<bean id="orderService" class="org.qiu.myspring.bean.OrderService">
<property name="orderDao" ref="orderDao"/>
</bean>
</beans>
@Test
public void testMySpring(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
Object vip = applicationContext.getBean("vip");
System.out.println(vip);
OrderService orderService = (OrderService) applicationContext.getBean("orderService");
orderService.generate();
}
运行结果:
一 叶 知 秋,奥 妙 玄 心