1. 材料准备
需要管理的类对象
- UserDAO
public class UserDAO {
public void insert(){
System.out.println("数据库保存数据中...");
}
}
- UserService
public class UserService {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public void save(){
System.out.println("业务保存数据中...");
userDAO.insert();
}
}
配置文件myspring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--使用myspring的开发人员写的 -->
<beans>
<bean id = "userBean" class = "com.example.myspring.bean.User">
<property name = "name" value = "张三"/>
<property name = "age" value = "11"/>
</bean>
<bean id = "userDAOBean" class="com.example.myspring.bean.UserDAO"/>
<bean id = "userServiceBean" class="com.example.myspring.bean.UserService">
<property name="userDAO" ref = "userDAOBean"/>
</bean>
</beans>
2. 核心接口
2.1 核心接口定义
观察以前写过的spring代码,我们使用spring框架的第一步就是拿到一个ApplicationContext
对象
然后就是用此应用域管理对象来获取Bean
对象,因此我们第一步就是要先把这个对象造出来。
- 定义核心接口,这里为了避免繁琐的细节,只实现一个最核心的方法
getBean()
当然了后续也可以通过泛型以及传入的类类型来改造这个方法
public interface ApplicationContext {
/**
* 根据bean的名称获取bean对象
* @param beanName
* @return
*/
Object getBean(String beanName);
}
- 然后我们定义核心接口实现类,具体定义如下:
public class ClassPathXMLApplicationContext implements ApplicationContext{
public ClassPathXMLApplicationContext(String configPath) {
//1.解析mySpring的配置文件
//2.初始化所有的bean对象
}
@Override
public Object getBean(String beanName) {
return null;
}
}
现在要思考的问题是,如果我们解析完了xml的配置文件,然后获得了所有关于这些bean的信息,具备了创建这些bean的条件,那么我们要将这些bean放到哪里去呢?
在前文中,详细探讨了关于bean
建立过程中的存放位置的问题
回顾
在Spring底层具有三级缓存,这三级缓存分别描述如下
- 一级缓存:一个大Map集合,key是bean的id,value是这个id对应的完整的bean对象(已经进行了属性赋值)
- 二级缓存:一个大Map集合,key是bean的id,value是这个id对应的半成品bean对象(为了解决循环依赖而提前曝光的bean对象)
- 三级缓存:一个大Map集合,key是bean的id,value是这个id对应的bean对象的工厂。
那么我们依葫芦画瓢,我们先从简单开始,实现一级缓存。
private Map<String,Object> singletonObjects = new HashMap<>();
2.2 核心接口实现
然后我们来实现关于获取所有bean的代码。
第一步:我们首先要获取一个关于这个文档文件的封装起来的重量级对象,做法是得到一个指向该文件的输入流
public ClassPathXMLApplicationContext(String configPath) {
//1.解析mySpring的配置文件,注意这个配置文件应该是从类路径中读取的,一定要是类路径!!!
SAXReader saxReader = new SAXReader();//解析XML文件的核心对象文件
//(1)既然要读取文件,那么肯定就是需要拿到一个输入流,通过这个输入流,拿到这个文件的数据
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configPath);
try {
//(2)这一步就是将xml配置文件封装为一个重量级的document对象,它包含了配置文件中所有的信息
Document document = saxReader.read(in);
}
}
第二步:获取这个文件中所有的bean标签,并且进行遍历
//(3)获取所有的bean标签
//tip:这是一个xpath的写法,如果你想要获取这个xml文件下的所有的这个名字的标签,写法就是//+标签名
List<Node> nodeList = document.selectNodes("//bean");
//(4)遍历你的bean标签,测试.....
nodeList.forEach((System.out::println));
测试结果:
org.dom4j.tree.DefaultElement@129a8472 [Element: <bean attributes: [org.dom4j.tree.DefaultAttribute@67b6d4ae [Attribute: name id value "userBean"], org.dom4j.tree.DefaultAttribute@34b7bfc0 [Attribute: name class value "com.example.myspring.bean.User"]]/>]
org.dom4j.tree.DefaultElement@e580929 [Element: <bean attributes: [org.dom4j.tree.DefaultAttribute@366e2eef [Attribute: name id value "userDAOBean"], org.dom4j.tree.DefaultAttribute@6df97b55 [Attribute: name class value "com.example.myspring.bean.UserDAO"]]/>]
org.dom4j.tree.DefaultElement@7c75222b [Element: <bean attributes: [org.dom4j.tree.DefaultAttribute@3cbbc1e0 [Attribute: name id value "userServiceBean"], org.dom4j.tree.DefaultAttribute@35fb3008 [Attribute: name class value "com.example.myspring.bean.UserService"]]/>]
第三步:针对于每一个bean的信息,我们通过反射产生其实例,并加入到map集合中进行提前曝光
//(6)获取id、class属性
String id = element.attributeValue("id");
String className = element.attributeValue("class");
//(7)通过反射机制创建对象,将其放到Map集合中,提前曝光
//这个Bean是没有经过set的赋值的,属于提前曝光的bean
Object newBean = Class.forName(className).newInstance();
singletonObjects.put(id,newBean);
第四步,取到每一个bean的属性,准备进行set注入赋值
nodeList.forEach(node -> {
Element element = (Element) node;
String id = element.attributeValue("id");
String className = element.attributeValue("class");
Class clazz = Class.forName(className);
//获取该标签下的所有property
//写法,注意:之前是获取整个文档中的某个属性
//而我现在是要获取当前标签下的所有的属性
List<Element> properties = element.elements("property");
//遍历所有的属性标签,取值,取值是根据标签中的属性来获取的
properties.forEach(property->{
//<property name = "name" value = "张三"/>
String name = property.attributeValue("name");
String value = property.attributeValue("value");
});
第五步,反射注入属性值
到了这一步,我们需要思考的问题是,我们注入的值类型是否是简单类型,也就是说有两种类型,一种类型是value
,一种类型是ref
。
properties.forEach(property->{
try{
//<property name = "name" value = "张三"/>
String name = property.attributeValue("name");
String value = property.attributeValue("value");
String ref = property.attributeValue("ref");
//然后调用set方法设置属性
Object instance = singletonObjects.get(id);
//拼装set方法
String setMethodName = "set"+name.toUpperCase(Locale.ROOT).charAt(0)+name.substring(1);
//这一步比较繁琐
//如果我们要对属性进行赋值,那么就是要调用set方法进行注入,然而我们的value这个值是String类型的
//因此我还需要获取这个对象中这个值的真实类型
Field declaredField = clazz.getDeclaredField(name);
//然后还要对这个属性进行转型以便赋值
if(value!=null){
//说明这个值是简单类型,简单类型比较麻烦,要将String转化为它指定的类型
//获取属性类型名
String simpleName = declaredField.getType().getSimpleName();
Object argValue = ParseUtils.getValue(simpleName, value);
clazz.getMethod(setMethodName,declaredField.getType()).invoke(instance,argValue);
}
if(ref!=null){
//说明这个值是非简单类型,是有beanId指向的具体的值
Object injectObject = singletonObjects.get(ref);
clazz.getMethod(setMethodName,declaredField.getType()).invoke(instance,injectObject);
}
}
工具类的实现
public static Object getValue(String typeName,String propertyValue){
Object propertyVal = null;
switch (typeName){
case "byte": case "Byte":
propertyVal = Byte.valueOf(propertyValue);
break;
case "short": case "Short":
propertyVal = Short.valueOf(propertyValue);
break;
case "int": case "Integer":
propertyVal = Integer.valueOf(propertyValue);
break;
case "long": case "Long":
propertyVal = Long.valueOf(propertyValue);
break;
case "float": case "Float":
propertyVal = Float.valueOf(propertyValue);
break;
case "double": case "Double":
propertyVal = Double.valueOf(propertyValue);
break;
case "boolean": case "Boolean":
propertyVal = Boolean.valueOf(propertyValue);
break;
case "char": case "Character":
propertyVal = propertyValue.charAt(0);
break;
case "String":
propertyVal = propertyValue;
break;
}
return propertyVal;
}
最终返回getBean()方法即可
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
至此,一个基于setter
的最核心的框架就已经完成了。完整代码如下
package org.myspringframework.spring.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
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.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* 功能描述
*
* @author: 张庭杰
* @date: 2022年11月24日 15:31
*/
public class ClassPathXMLApplicationContext implements ApplicationContext{
private Map<String,Object> singletonObjects = new HashMap<>();
private static final Logger log = LoggerFactory.getLogger(ClassPathXMLApplicationContext.class);
public ClassPathXMLApplicationContext(String configPath) {
//1.解析mySpring的配置文件,注意这个配置文件应该是从类路径中读取的,一定要是类路径,否则的话是拿不到输入流的
//如果你非要写绝对路径等也可以,你就用传统的I/O流也能够完成这个功能
//只不过这个类是ClassPath专属的配置文件,这样实现比较好
SAXReader saxReader = new SAXReader();//解析XML文件的核心对象文件
//(1)既然要读取文件,那么肯定就是需要拿到一个输入流,通过这个输入流,拿到这个文件的数据
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configPath);
try {
//(2)这一步就是将xml配置文件封装为一个重量级的document对象,它包含了配置文件中所有的信息
Document document = saxReader.read(in);
//(3)获取所有的bean标签
//tip:这是一个xpath的写法,如果你想要获取这个xml文件下的所有的这个名字的标签,写法就是//+标签名
List<Node> nodeList = document.selectNodes("//bean");
//(4)遍历你的bean标签,测试.....
nodeList.forEach((node -> {
try {
//(5)当你获取到这个node对象的时候,转型为element对象
//这是一种解析xml文件的常见手段,这是因为通常获取得到的node都能够向下转型为此对象
//而且这个对象的方法更加丰富,能够帮助我们获得更多信息
Element element = (Element) node;
//(6)获取id、class属性
String id = element.attributeValue("id");
String className = element.attributeValue("class");
//(7)通过反射机制创建对象,将其放到Map集合中,提前曝光
//这个Bean是没有经过set的赋值的,属于提前曝光的bean
Object newBean = Class.forName(className).newInstance();
singletonObjects.put(id,newBean);
} catch (Exception e) {
e.printStackTrace();
}
}));
//2.初始化所有的bean对象
nodeList.forEach(node -> {
try {
Element element = (Element) node;
String id = element.attributeValue("id");
String className = element.attributeValue("class");
Class clazz = Class.forName(className);
//获取该标签下的所有property
//写法,注意:之前是获取整个文档中的某个属性
//而我现在是要获取当前标签下的所有的属性
List<Element> properties = element.elements("property");
//遍历所有的属性标签,取值,取值是根据标签中的属性来获取的
properties.forEach(property->{
try{
//<property name = "name" value = "张三"/>
String name = property.attributeValue("name");
String value = property.attributeValue("value");
String ref = property.attributeValue("ref");
//然后调用set方法设置属性
Object instance = singletonObjects.get(id);
//拼装set方法
String setMethodName = "set"+name.toUpperCase(Locale.ROOT).charAt(0)+name.substring(1);
//这一步比较繁琐
//如果我们要对属性进行赋值,那么就是要调用set方法进行注入,然而我们的value这个值是String类型的
//因此我还需要获取这个对象中这个值的真实类型
Field declaredField = clazz.getDeclaredField(name);
//然后还要对这个属性进行转型以便赋值
if(value!=null){
//说明这个值是简单类型,简单类型比较麻烦,要将String转化为它指定的类型
//获取属性类型名
String simpleName = declaredField.getType().getSimpleName();
Object argValue = ParseUtils.getValue(simpleName, value);
clazz.getMethod(setMethodName,declaredField.getType()).invoke(instance,argValue);
}
if(ref!=null){
//说明这个值是非简单类型,是有beanId指向的具体的值
Object injectObject = singletonObjects.get(ref);
clazz.getMethod(setMethodName,declaredField.getType()).invoke(instance,injectObject);
}
}catch (Exception e){
e.printStackTrace();
}
});
}catch (Exception e){
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
测试代码
public void testContext(){
ApplicationContext applicationContext = new ClassPathXMLApplicationContext("myspring.xml");
Object userServiceBean = applicationContext.getBean("userServiceBean");
Object userBean = applicationContext.getBean("userBean");
System.out.println(userBean);
Object userDAOBean= applicationContext.getBean("userDAOBean");
System.out.println(userDAOBean);
UserService serviceBean = (UserService) userServiceBean;
serviceBean.save();
}