手寫Spring框架

Rainbow-Sea發表於2024-05-05

1. 手寫Spring框架

@

目錄
  • 1. 手寫Spring框架
  • 每博一文案
  • 2. 反射機制的回顧
  • 3. 開始手寫 Spring 框架
    • 3.1 第一步:使用 IDE 建立模組myspring
    • 3.2 第二步:準備好我們要管理的Bean
    • 3.3 第三步:準備myspring.xml配置檔案
    • 3.4 第四步:編寫ApplicationContext介面
    • 3.5 第五步:編寫ClassPathXmlApplicationContext
    • 3.6 第六步:確定採用Map集合儲存Bean
    • 3.7 第七步:解析配置檔案,並例項化所有Bean
    • 3.8 第八步:測試能否獲取到Bean
    • 3.9 第九步:給Bean的屬性賦值
    • 3.10 第十步:測試是否能夠正常賦值成功
    • 3.11 第十一步:打包釋出
    • 3.12 第十二步: 站在程式設計師角度使用我們自己手寫的 spring 的 myspring框架
  • 4. 總結:
  • 5. 最後:


每博一文案

特別喜歡一種人
他們的知識儲備和資訊密度,都遠遠高於我
但還是願意認真聽我說,那些沒有營養的廢話
我始終覺得,溫柔浪漫跟博學,是人身上最難得的特性
懂得向下相容的人,走到哪裡都是寶藏
太喜歡哪些優秀,卻不帶優越感的人相處了

2. 反射機制的回顧

我們知道框架一般是由 設計模式+註解+反射 來實現,並進行運用開發的。

所以我們要手寫 Spring 框架,就需要先來回顧,回顧Java當中的反射機制,這裡是簡單的回顧反射 。關於反射機制更加詳細的內容,大家可以移步至✏️✏️✏️ Java “框架 = 註解 + 反射 + 設計模式” 之 反射詳解-CSDN部落格

我們知道,想要呼叫一個方法,就需要明確知道該方法的四個要素:

  1. 呼叫的是哪個物件的
  2. 哪個方法
  3. 該方法傳什麼引數
  4. 有無返回值,有返回值的話,又該返回什麼值

為了簡單的回顧我們的反射機制,下面我們進行一個簡單的任務。完成下面的需求

首先你知道以下這幾條資訊

假設你現在以下資訊:
 1.有這樣一個類,類名叫做:com.rainbowsea.reflect.User
 2.這個類符合javabean規範,屬性私有化,對外提供公開的setter和getter方法。
 3.你還知道這個類當中有一個屬性,屬性的名字叫做 age
 4.並且你還知道age屬性的型別是 int 型別
請使用反射機制呼叫set()方法,給 User 物件的age 屬性賦值(age 屬性賦值為 20 )

在這裡插入圖片描述

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 屬性值進行賦值操作。在這裡插入圖片描述

在這裡插入圖片描述

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 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

在這裡插入圖片描述

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 +
                '}';
    }
}

package com.rainbowsea.myspring.bean;

public class UserDao {
    public void insert() {
        System.out.println("資料庫插入資料");
    }
}

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 配置檔案。

在這裡插入圖片描述

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方法。

在這裡插入圖片描述

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 屬性的值

在這裡插入圖片描述
在這裡插入圖片描述

在這裡插入圖片描述
在這裡插入圖片描述

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

在這裡插入圖片描述

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
 */

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

完整程式碼:

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 框架

在這裡插入圖片描述
在這裡插入圖片描述

在這裡插入圖片描述

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 +
                '}';
    }
}

package com.rainbowsea.myspring.bean;

public class OrderDao {
    public void insert() {
        System.out.println("插入資料");
    }
}

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. 最後:

“在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。”

在這裡插入圖片描述

相關文章