手写Spring框架

1. 手写Spring框架

文章目录

  • [1. 手写Spring框架](#1. 手写Spring框架)
  • 每博一文案
  • [2. 反射机制的回顾](#2. 反射机制的回顾)
  • [3. 开始手写 Spring 框架](#3. 开始手写 Spring 框架)
    • [3.1 第一步:使用 IDE 创建模块myspring](#3.1 第一步:使用 IDE 创建模块myspring)
    • [3.2 第二步:准备好我们要管理的Bean](#3.2 第二步:准备好我们要管理的Bean)
    • [3.3 第三步:准备myspring.xml配置文件](#3.3 第三步:准备myspring.xml配置文件)
    • [3.4 第四步:编写ApplicationContext接口](#3.4 第四步:编写ApplicationContext接口)
    • [3.5 第五步:编写ClassPathXmlApplicationContext](#3.5 第五步:编写ClassPathXmlApplicationContext)
    • [3.6 第六步:确定采用Map集合存储Bean](#3.6 第六步:确定采用Map集合存储Bean)
    • [3.7 第七步:解析配置文件,并实例化所有Bean](#3.7 第七步:解析配置文件,并实例化所有Bean)
    • [3.8 第八步:测试能否获取到Bean](#3.8 第八步:测试能否获取到Bean)
    • [3.9 第九步:给Bean的属性赋值](#3.9 第九步:给Bean的属性赋值)
    • [3.10 第十步:测试是否能够正常赋值成功](#3.10 第十步:测试是否能够正常赋值成功)
    • [3.11 第十一步:打包发布](#3.11 第十一步:打包发布)
    • [3.12 第十二步: 站在程序员角度使用我们自己手写的 spring 的 myspring框架](#3.12 第十二步: 站在程序员角度使用我们自己手写的 spring 的 myspring框架)
  • [4. 总结:](#4. 总结:)
  • [5. 最后:](#5. 最后:)

每博一文案

tex 复制代码
特别喜欢一种人
他们的知识储备和信息密度,都远远高于我
但还是愿意认真听我说,那些没有营养的废话
我始终觉得,温柔浪漫跟博学,是人身上最难得的特性
懂得向下兼容的人,走到哪里都是宝藏
太喜欢哪些优秀,却不带优越感的人相处了

2. 反射机制的回顾

我们知道框架一般是由 设计模式+注解+反射 来实现,并进行运用开发的。

所以我们要手写 Spring 框架,就需要先来回顾,回顾Java当中的反射机制,这里是简单的回顾反射 。关于反射机制更加详细的内容,大家可以移步至✏️✏️✏️ Java "框架 = 注解 + 反射 + 设计模式" 之 反射详解-CSDN博客

我们知道,想要调用一个方法,就需要明确知道该方法的四个要素:

  1. 调用的是哪个对象的
  2. 哪个方法
  3. 该方法传什么参数
  4. 有无返回值,有返回值的话,又该返回什么值

为了简单的回顾我们的反射机制,下面我们进行一个简单的任务。完成下面的需求

首先你知道以下这几条信息

java 复制代码
假设你现在以下信息:
 1.有这样一个类,类名叫做:com.rainbowsea.reflect.User
 2.这个类符合javabean规范,属性私有化,对外提供公开的setter和getter方法。
 3.你还知道这个类当中有一个属性,属性的名字叫做 age
 4.并且你还知道age属性的类型是 int 类型
请使用反射机制调用set()方法,给 User 对象的age 属性赋值(age 属性赋值为 20 )
java 复制代码
package com.rainbowsea.reflect;

public class User {
    private String name;
    private int 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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

下面通过反射机制,获取到为 age 属性值进行赋值操作。

java 复制代码
package com.rainbowsea.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test2 {
    /*
    需求:
    假设你现在以下信息:
    1.有这样一个类,类名叫做:com.powernode.reflect.User
    2.这个类符合javabean规范,属性私有化,对外提供公开的setter和getter方法。
    3.你还知道这个类当中有一个属性,属性的名字叫做 age
    4.并且你还知道age属性的类型是 int 类型
   请使用反射机制调用set()方法,给 User 对象的age 属性赋值
     */

    public static void main(String[] args) {
        String className = "com.rainbowsea.reflect.User";
        String propertyName = "age";


        try {
            // 通过 反射机制调用setAge(int) 方法
            // 获取到对应的类
            Class<?> clazz = Class.forName(className);
            // 获取对应的方法
            // 方法名: 这里我们知道set 方法格式为: setAge() set+属性名的首字母大小+后面属性名的小写字母
            String setMethodName = "set" + propertyName.toUpperCase().charAt(0)+ propertyName.substring(1);
            // 获取对应的方法名:
            // 我们知道的属性名就可以,根据属性名获取属性类型
            // 下面这里获取到的是一个完整的:private int com.rainbowsea.reflect.User.age 类型名
            Field field = clazz.getDeclaredField(propertyName);

            // field.getType() 获取到属性的类型,是个简单的类型 int
            Method declaredMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
            //System.out.println(field.getType());
            
            // 准备对象
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
            Object obj = declaredConstructor.newInstance();
            // 调用set()方法赋值, 没有返回值(调用 obj 的对象中的,declaredMethod()方法,参数是 30,没有返回值)
            declaredMethod.invoke(obj,30);

            System.out.println(obj);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }





    }
}

成功了,哪我们反射机制就算简单回顾完了,该上正菜了------》手写 Spring 框架


3. 开始手写 Spring 框架

Spring IoC容器的实现原理:工厂模式 + 解析XML + 反射机制

我们给自己的框架起名为:myspring(我的春天)

3.1 第一步:使用 IDE 创建模块myspring

这里我们采用 IDE 当中的Maven 创建一个 名为 myspring的 Module。

注意:这里是 org.myspringframework 我们现在的角色是一个spring开发者人员。所以我们的包名的命名格式还是要规定一下的。

打包方式采用 jar ,并且还需要引入 dom4jjaxen 的依赖,因为要使用它解析XML 文件,还有junit 依赖。(进行测试)。有上面三个就可以了,这里的话,我再引入一个 log4j2 的依赖,用于日志信息的显示记录。因为我们这里是,自己手写 Spring 框架,所以就不要再引入 Spring的 jar 包内容了。


xml 复制代码
<?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>com.rainbowsea</groupId>
    <artifactId>myspring</artifactId>
    <version>1.0-SNAPSHOT</version>
<!--    打包方式为 jar -->
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
<!--        dom4j是一个能够解析xml文件的java组件-->
        <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>

<!--        log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>
    </dependencies>

</project>

3.2 第二步:准备好我们要管理的Bean

准备好我们要管理的Bean(这些Bean在将来开发完框架之后是要删除的

注意包名,不要用 org.myspringframework包了,因为这些Bean不是框架内置的,是将来使用我们写好 Spring 框架方便我们后续测试,基于便于我们手写 Spring 框架进行思考,所以这里我就定义为了。com.rainbowsea.myspring.bean 大家可以自行定义

一共定义了 三个 Bean :User,UserService,UserDao

java 复制代码
package com.rainbowsea.myspring.bean;

public class User {
    private String name;
    private int age;


    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java 复制代码
package com.rainbowsea.myspring.bean;

public class UserDao {
    public void insert() {
        System.out.println("数据库插入数据");
    }
}
java 复制代码
package com.rainbowsea.myspring.bean;

public class UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }


    public void save() {
        userDao.insert();
    }
}

3.3 第三步:准备myspring.xml配置文件

将来在框架开发完毕之后,这个文件也是要删除的。因为这个配置文件的提供者应该是使用这个框架的程序员。这里我是同样也是为了,用于验证测试,我们手写额Spring框架的。文件名随意,我们这里叫做:myspring.xml。文件放在类路径当中即可,我们这里把文件放到类的根路径下。使用value给简单属性赋值。使用ref给非简单属性赋值。

因为上面我配置了引入一个 log4j2 的依赖,用于日志信息的显示记录。所以这里,我们还需配置一个关于 log4j2 的配置文件。

3.4 第四步:编写ApplicationContext接口

ApplicationContext 接口中提供一个getBean()方法,通过该方法可以获取Bean对象。

注意包名:这个接口就是myspring框架中的一员了。

3.5 第五步:编写ClassPathXmlApplicationContext

ClassPathXmlApplicationContext 是 ApplicationContext 接口的实现类。该类从类路径当中加载myspring.xml 配置文件。

java 复制代码
package org.myspringframework.core;

public class ClassPathXmlApplicationContext implements ApplicationContext {

    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

3.6 第六步:确定采用Map集合存储Bean

确定采用Map集合存储Bean实例。Map集合的key存储 beanId,value存储Bean实例。Map<String,Object>

在ClassPathXmlApplicationContext类中添加 Map<String,Object>属性。

并且在ClassPathXmlApplicationContext类中添加构造方法,该构造方法的参数接收myspring.xml文件。

同时实现getBean方法。

java 复制代码
package org.myspringframework.core;

import java.util.HashMap;
import java.util.Map;


public class ClassPathXmlApplicationContext implements ApplicationContext {


    // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
    private Map<String, Object> singletonObjects = new HashMap<>();

    }


    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

3.7 第七步:解析配置文件,并实例化所有Bean

在 ClassPathXmlApplicationContext 的构造方法中解析配置文件,获取所有bean的类名,通过反射机制调用无参数构造方法创建Bean。并且将Bean对象存放到Map集合中。

补充:

首先我们需要,获取到一个名为 SAXReader(这是dom4j 解析xml 文件的核心对象) 的对象以及Document ,用来读取我们当中的myspring.xml'所含的 bean 的配置信息。这两个对象可以读取到 xml 配置文件的信息。


同时需要:注意的是:在 java 当中一个 "//" 才表示为一个 /

注意一个向下转型:向下转型的目的是为了使用Element接口更加丰富的方法。

该 Element 可以获取到 <bean> 标签当中的 id 的属性值,和 <bean>标签当中的 class 属性的值




java 复制代码
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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class ClassPathXmlApplicationContext implements ApplicationContext {


    // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
    private Map<String, Object> singletonObjects = new HashMap<>();

    /**
     * 解析myspring的配置文件,然后初始化所有的Bean对象
     *
     * @param configLocation spring配置文件的路径,注意:使用的是ClassPathXmlApplicationContext ,所以
     *                       配置文件应当放到类路径下
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
        // 这是dom4j 解析xml 文件的核心对象
        SAXReader saxReader = new SAXReader();
        Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);


        // 获取一个输入流,指向配置文件
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
        try {
            // 读文件
            Document document = saxReader.read(in);

            // 获取到所有的 <bean>标签
            // 注意要两个//,才能表示一个 /
            List<Node> nodes = document.selectNodes("//bean");

            // 遍历 bean标签
            nodes.forEach(node -> {
                //System.out.println(node);
                // 向下转型的目的是为了使用Element接口更加丰富的方法
                Element beanElt = (Element) node;

                // 获取 <bean>标签当中的id的属性
                String id = beanElt.attributeValue("id");

                // 获取 <bean>标签当中的 class 属性
                String className = beanElt.attributeValue("class");
                //logger.info("beanName" + id);
                //logger.info("beanClassName " + className);

                try {
                    // 通过反射机制创建对象,将其放到Map集合中,提前曝光
                    // 获取class对象
                    Class<?> aClass = Class.forName(className);
                    // 获取无参构造方法实例化Bean
                    Constructor<?> declaredCon = aClass.getDeclaredConstructor();
                    // 调用无参构造方法实例化Bean
                    Object bean = declaredCon.newInstance();
                    // 将Bean 曝光,加入 Map集合
                    singletonObjects.put(id, bean);
                    // 记录日志
                    logger.info(singletonObjects.toString());
                } catch (Exception e) {
                e.printStackTrace();
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

3.8 第八步:测试能否获取到Bean

java 复制代码
package com.rainbowsea.myspring.test;

import com.rainbowsea.myspring.bean.UserService;
import org.junit.Test;
import org.myspringframework.core.ApplicationContext;
import org.myspringframework.core.ClassPathXmlApplicationContext;

public class MySpringTest {
    @Test
    public void testMySpring() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
        Object user = applicationContext.getBean("user");
        System.out.println(user);

    }
}

我们的 myspring.xml 配置文件当中,有三个 bean 对象的配置信息,所以下面应当显示三个 Bean的实例对象。如下显示结果,我们可以看出,是成功获取到了 myspring.xml 配置文件当中所有的 Bean 对象了。

通过测试Bean已经实例化成功了,属性的值是null,这是我们能够想到的,毕竟我们调用的是无参数构造方法,所以属性都是默认值。

下一步就是我们应该如何给Bean的属性赋值了。

3.9 第九步:给Bean的属性赋值

通过反射机制调用set方法,给Bean的属性赋值。

继续在ClassPathXmlApplicationContext构造方法中编写代码。
补充说明:

我们需要,再次重新把所有的 Bean 标签遍历一次,这一次主要是给对象属性赋值的

获取到 获取该标签下的所有的属性为 <property>标签,因为 在 <property> 标签当中存放着,我们对属性赋值要的确切的值。

赋值,这里我们是通过 set 方法进行赋值的,这个简单,我们可以通过反射(对象,方法名,方法参数类型)获取到方法的对象,又可以通过属性名,获取到属性的类型。

这里最难的部分就是,在我们的 在 <property> 标签当中存放着,我们对属性赋值要的确切的值。但是这个值有两种:简单类型 value 的值,复杂类型 ref 的值。复杂类型就是引用类型,这个我们以及在曝光的时候,就存储到Map当中去了,我们只需要通过对应的beanName 从 Map当前取出来,再通过反射机制调用set方法进行一个赋值。


简单类型的赋值操作:

简单类型的复杂点,就是一个转换,我们再 myspring.xml 配置文件当中的,属性值都是以字符串的形式存在的,而我们的实际属性的类型,可能是 int,char,double 等等类型的,String 字符串类型不可以直接赋值到其他 int,char 类型当中去,所以我们就需要对我们所赋值的属性进行一个判断,如果该类型是 int 类型,我们就需要将 String 字符串类型转换为我们所需要的 int 类型。

这里我们说明一下:

复制代码
我们myspring框架说明以下,我们只支持这些类型为简单类型
byte short int long float double boolean char
Byte Short Intger Long Float Double Boolean Character
String
 */



完整代码:

java 复制代码
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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class ClassPathXmlApplicationContext implements ApplicationContext {


    // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
    private Map<String, Object> singletonObjects = new HashMap<>();

    /**
     * 解析myspring的配置文件,然后初始化所有的Bean对象
     *
     * @param configLocation spring配置文件的路径,注意:使用的是ClassPathXmlApplicationContext ,所以
     *                       配置文件应当放到类路径下
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        // 解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
        // 这是dom4j 解析xml 文件的核心对象
        SAXReader saxReader = new SAXReader();
        Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);


        // 获取一个输入流,指向配置文件
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
        try {
            // 读文件
            Document document = saxReader.read(in);

            // 获取到所有的 <bean>标签
            // 注意要两个//,才能表示一个 /
            List<Node> nodes = document.selectNodes("//bean");

            // 遍历 bean标签
            nodes.forEach(node -> {
                //System.out.println(node);
                // 向下转型的目的是为了使用Element接口更加丰富的方法
                Element beanElt = (Element) node;

                // 获取 <bean>标签当中的id的属性
                String id = beanElt.attributeValue("id");

                // 获取 <bean>标签当中的 class 属性
                String className = beanElt.attributeValue("class");
                //logger.info("beanName" + id);
                //logger.info("beanClassName " + className);

                try {
                    // 通过反射机制创建对象,将其放到Map集合中,提前曝光
                    // 获取class对象
                    Class<?> aClass = Class.forName(className);
                    // 获取无参构造方法实例化Bean
                    Constructor<?> declaredCon = aClass.getDeclaredConstructor();
                    // 调用无参构造方法实例化Bean
                    Object bean = declaredCon.newInstance();
                    // 将Bean 曝光,加入 Map集合
                    singletonObjects.put(id, bean);
                    // 记录日志
                    //logger.info(singletonObjects.toString());
                    //System.out.println(singletonObjects.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }


            });

            // 再次重新把所有的 Bean 标签遍历一次,这一次主要是给对象属性赋值的
            nodes.forEach(node -> {
                Element beanElt = (Element) node;
                // 获取 <bean>标签当中的 id 属性
                String id = beanElt.attributeValue("id");

                // 获取<bean>标签当中的 class 属性的值
                String className = beanElt.attributeValue("class");

                // 获取Class 对象通过全限定类名+反射机制


                try {
                    Class<?> aClass = Class.forName(className);
                    // 获取该<bean>标签下的所有的属性为 <property>标签
                    List<Element> propertys = beanElt.elements("property");
                    // 遍历<property> 的所有的属性标签
                    propertys.forEach(property -> {
                        // 获取 <property> 标签下的name 属性名下的值
                        String propertyName = property.attributeValue("name");
                        //logger.info(" <property> 标签下的name 属性名 " + propertyName);
                        // 获取 <property> 标签下的name 属性名下的值

                        // 获取 set 方法名 set+属性名第一个单词大写
                        String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);

                        try {
                            //获取 set 方法
                            // 获取属性类型,通过属性名,可以获取到属性类型
                            Field declaredField = aClass.getDeclaredField(propertyName);
                            // 获取set方法
                            Method setMethod = aClass.getDeclaredMethod(setMethodName, declaredField.getType());
                            // 调用set方法(set方法没有返回值)

                            // 获取 <property> 标签当中的 value 或者是 ref 的具体的值
                            String value = property.attributeValue("value");
                            String ref = property.attributeValue("ref");

                            Object actualValue = null; // 真值
                            if (value != null) {
                                // 说明在<property> 标签当中  这个值是简单类型
                                /*
                                我们myspring框架说明以下,我们只支持这些类型为简单类型
                                byte short int long float double boolean char
                                Byte Short Intger Long Float Double Boolean Character
                                String
                                 */
                                // 获取属性类型名
                                String propertyTypeSimpleName = declaredField.getType().getSimpleName();
                                switch (propertyTypeSimpleName) {
                                    case "byte":
                                        actualValue = Byte.parseByte(value);
                                        break;
                                    case "short":
                                        actualValue = Short.parseShort(value);
                                        break;
                                    case "int":
                                        actualValue = Integer.parseInt(value);
                                        break;
                                    case "long":
                                        actualValue = Long.parseLong(value);
                                        break;
                                    case "float":
                                        actualValue = Float.parseFloat(value);
                                        break;
                                    case "double":
                                        actualValue = Double.parseDouble(value);
                                        break;
                                    case "boolean":
                                        actualValue = Boolean.parseBoolean(value);
                                        break;
                                    case "char":
                                        actualValue = value.charAt(0);
                                        break;
                                    case "Byte":
                                        actualValue = Byte.valueOf(value);
                                        break;
                                    case "Short":
                                        actualValue = Short.valueOf(value);
                                        break;
                                    case "Integer":
                                        actualValue = Integer.valueOf(value);
                                        break;
                                    case "Long":
                                        actualValue = Long.valueOf(value);
                                        break;
                                    case "Float":
                                        actualValue = Float.valueOf(value);
                                        break;
                                    case "Double":
                                        actualValue = Double.valueOf(value);
                                        break;
                                    case "Boolean":
                                        actualValue = Boolean.valueOf(value);
                                        break;
                                    case "Character":
                                        // char 仅仅只是一个字符,所以要获取第一个字符
                                        actualValue = value.charAt(0);
                                        break;
                                    case "String":
                                        actualValue = value;
                                        break;


                                }
                                setMethod.invoke(singletonObjects.get(id), actualValue);
                            }

                            if (ref != null) {
                                // 说明这个值是非简单类型
                                // 因为是非简单类型,那么它提前曝光的,就存放到了集合当前去了。
                                setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref));

                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    });

                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }

            });


        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }


    }


    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

3.10 第十步:测试是否能够正常赋值成功

注意: 不要导错包了,我们这里导入的是,我们自己手写的Spring框架的包。



3.11 第十一步:打包发布

将多余的类以及配置文件删除,当然,我们这里并不是真正的开发 Spring 框架,所以不删除也是没有关系的(只是需要注意不要导错jar 包了)。这里我们使用maven打包发布。关于 Maven 的使用上的内容,想要了解更多的。大家可以移步:✏️✏️✏️ Maven_ChinaRainbowSea的博客-CSDN博客。各种细节内容都是截屏标志了的,这个大家放心。




3.12 第十二步: 站在程序员角度使用我们自己手写的 spring 的 myspring框架

第一步: 我们新建一个名为: myspring-text 的模块,进行一个我们手写的Spring 的框架的测试。


第二步: 引入我们自己编写的Spring 框架,不要引错了。


第三步: 编写准备一些Bean 对象用于测试,我们自己编写的 Spring 框架


java 复制代码
package com.rainbowsea.myspring.bean;

public class Vip {

    private String name;
    private int age;
    private double 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;
    }

    @Override
    public String toString() {
        return "Vip{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}
java 复制代码
package com.rainbowsea.myspring.bean;

public class OrderDao {
    public void insert() {
        System.out.println("插入数据");
    }
}
java 复制代码
package com.rainbowsea.myspring.bean;

public class OrderService {
    private OrderDao orderDao;

    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    public void generate() {
        orderDao.insert();
    }
}

第四步: 运行,测试,我们编写的Spring 的框架是否成功。

注意:导入的是我们自己编写的Spring 框架。不要导错了。

从我们的运行结果上看,我们编写的Spring 框架是没有问题的。

4. 总结:

  1. 框架一般是由 设计模式+注解+反射 来实现,并进行运用开发的。而这里我们的Spring IoC容器的实现原理:工厂模式 + 解析XML + 反射机制

  2. 我们知道,想要调用一个方法,就需要明确知道该方法的四个要素:

    1. 调用的是哪个对象的
    2. 哪个方法
    3. 该方法传什么参数
    4. 有无返回值,有返回值的话,又该返回什么值
  3. 引入 dom4jjaxen 的依赖,因为要使用它解析XML 文件, SAXReader(这是dom4j 解析xml 文件的核心对象) 的对象以及Document ,用来读取我们当中的myspring.xml'所含的 bean 的配置信息。这两个对象可以读取到 xml 配置文件的信息。

  4. 编写Spring 框架比较难的点,就是:简单类型的复杂点,就是一个转换,我们再 myspring.xml 配置文件当中的,属性值都是以字符串的形式存在的,而我们的实际属性的类型,可能是 int,char,double 等等类型的,String 字符串类型不可以直接赋值到其他 int,char 类型当中去,所以我们就需要对我们所赋值的属性进行一个判断,如果该类型是 int 类型,我们就需要将 String 字符串类型转换为我们所需要的 int 类型。

  5. 注意:我们手写的Spring 框架不并不是完完全全按照,真正的Spring框架来编写的,这是大部分思路是一致的,对应不同需要上的处理可能不太一致,这一点希望大家可以明白。

  6. 我们手写Spring 框架是为了,更好的理解Spring 框架的原理,从而不惧怕Spring的使用,在使用上更加的灵活,得心易手。

5. 最后:

"在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。"

相关推荐
青云交几秒前
Java 大视界 -- Java 大数据在智能农业无人机植保作业路径规划与药效评估中的应用(165)
java·大数据·数据采集·路径规划·智能农业·无人机植保·药效评估
qq_340474029 分钟前
5.2.1 WPF 通过ItemControl自己做柱状图
java·开发语言·wpf
竹等寒9 分钟前
Go红队开发— 收官工具
开发语言·网络·后端·安全·golang
酷酷的名字9 分钟前
初识Docker,入门篇及实战操作
前端·后端
lqstyle9 分钟前
面试必备之jvm垃圾收集器
后端·面试
shepherd11113 分钟前
详解SQL查询语句关键字执行顺序
数据库·后端·mysql
探索为何17 分钟前
Go语言从零构建SQL数据库引擎(3)
后端
M1A117 分钟前
Java 时间处理:轻松掌握 java.time 包
java·后端
守护者17017 分钟前
JAVA学习-练习试用Java实现“实现一个Hadoop MapReduce任务,对大数据集中的日志数据进行解析和筛选”
java·学习
失乐园18 分钟前
万亿级吞吐背后的秘密:Kafka核心架构与源码级实战调优指南
java·后端·面试