从零开始学Spring-从零开始实现setter注入


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();

}

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录