打怪上分! 手寫Spring ioc 框架,狠狠的「擼擼」Spring 原始碼

2020-10-29 11:01:21

估計很多朋友使用 spring 很長時間,對於 spring 使用非常頻繁,實際上對於原始碼一直沒有靜下心來學習過。

你是不是有這樣的感覺呢?

但是 spring 原始碼存在一個問題,那就是過於抽象,導致學習起來成本上升。所以本文由淺入深,只實現 spring 的核心功能,便於自己和他人學習 spring 的核心原理。

文章有點長,耐心閱讀!如果對你有幫助,記得三連哦!

在這裡插入圖片描述

spring 的核心

Spring 的核心就是 spring-beans,後面的一切 spring-boot,spring-cloud 都是建立在這個地基之上。
當別人問你 spring 的時候,希望你可以談談自己對於 spring ioc 自己更深層的見解,而不是網上人云亦云的幾句話。

另外本人整理收藏了20年多家公司面試知識點整理 ,以及各種Java核心知識點免費分享給大家,下方只是部分截圖
想要資料的話也可以點選直接進入:暗號:csdn,免費獲取。

在這裡插入圖片描述

什麼是 IOC

控制反轉(Inversion of Control,縮寫為IoC),是物件導向程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。
其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI)。
通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的參照傳遞給它。
也可以說,依賴被注入到物件中。

為什麼需要 IOC

IoC 是解耦的一種方法。
我們知道Java 是一門物件導向的語言,在 Java 中 Everything is Object,我們的程式就是由若干物件組成的。
當我們的專案越來越大,合作的開發者越來越多的時候,我們的類就會越來越多,類與類之間的參照就會成指數級的增長。

在這裡插入圖片描述
這樣的工程簡直就是災難,如果我們引入 Ioc 框架。

由框架來維護類的生命週期和類之間的參照。

我們的系統就會變成這樣:
在這裡插入圖片描述
這個時候我們發現,我們類之間的關係都由 IoC 框架負責維護類,同時將類注入到需要的類中。

也就是類的使用者只負責使用,而不負責維護。

把專業的事情交給專業的框架來完成,大大的減少開發的複雜度。

快速開始

maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>ioc</artifactId>
    <version>0.1.11</version>
</dependency>

測試準備

全部測試程式碼,見 test 模組。

  • Apple.java
public class Apple {

    public void color() {
        System.out.println("Apple color: red. ");
    }

}
  • apple.json

類似於 xml 的設定,我們暫時使用 json 進行設定驗證。

[
{"name":"apple","className":"com.github.houbb.ioc.test.service.Apple"}
]

執行測試.

  • 測試
BeanFactory beanFactory = new JsonApplicationContext("apple.json");
Apple apple = (Apple) beanFactory.getBean("apple");
apple.color();
  • 紀錄檔
Apple color: red.

spring 基本實現流程

說明

spring-beans 一切都是圍繞 bean 展開的。

BeanFactory 負責對 bean 進行生命週期的相關管理,本節展示第一小節的簡單實現流程。

spring 核心流程

Spring IoC 主要是以下幾個步驟。

  1. 初始化 IoC 容器。

  2. 讀取組態檔。

  3. 將組態檔轉換為容器識別對的資料結構(這個資料結構在Spring中叫做 BeanDefinition)

  4. 利用資料結構依次範例化相應的物件

  5. 注入物件之間的依賴關係

BeanDefinition 的抽象

BeanDefinition 是 spring 對 java bean 屬性的一個抽象,經過這一層抽象,組態檔可以是 xml/json/properties/yaml 等任意一種,甚至包括註解掃包。
為 spring 的拓展帶來極大的靈活性。
本框架考慮到實現的簡單性,初步只實現了 json 和基於註解掃包兩種方式。
後期如果有時間可以考慮新增 xml 的實現,其實更多是 xml 的解析工作量,核心流程已經全部實現。

實現原始碼節選

BeanDefinition 相關

包含了對於 java bean 的基本資訊抽象。

  • BeanDefinition.java

其預設實現為 DefaultBeanDefinition.java,就是對介面實現的最基本的 java POJO

/**
 * 物件定義屬性
 * @author binbin.hou
 * @since 0.0.1
 */
public interface BeanDefinition {

    /**
     * 名稱
     * @return 名稱
     * @since 0.0.1
     */
    String getName();

    /**
     * 設定名稱
     * @param name 名稱
     * @since 0.0.1
     */
    void setName(final String name);

    /**
     * 類名稱
     * @return 類名稱
     */
    String getClassName();

    /**
     * 設定類名稱
     * @param className 類名稱
     * @since 0.0.1
     */
    void setClassName(final String className);

}

BeanFactory 核心管理相關

  • BeanFactory.java
/**
 * bean 工廠介面
 * @author binbin.hou
 * @since 0.0.1
 */
public interface BeanFactory {

    /**
     * 根據名稱獲取對應的範例資訊
     * @param beanName bean 名稱
     * @return 物件資訊
     * @since 0.0.1
     */
    Object getBean(final String beanName);

    /**
     * 獲取指定型別的實現
     * @param beanName 屬性名稱
     * @param tClass 型別
     * @param <T> 泛型
     * @return 結果
     * @since 0.0.1
     */
    <T> T getBean(final String beanName, final Class<T> tClass);

}

  • DefaultBeanFactory.java

為介面最基礎的實現,原始碼如下:

/**
 * bean 工廠介面
 * @author binbin.hou
 * @since 0.0.1
 */
public class DefaultBeanFactory implements BeanFactory {

    /**
     * 物件資訊 map
     * @since 0.0.1
     */
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    /**
     * 物件 map
     * @since 0.0.1
     */
    private Map<String, Object> beanMap = new ConcurrentHashMap<>();

    /**
     * 註冊物件定義資訊
     * @since 0.0.1
     */
    protected void registerBeanDefinition(final String beanName, final BeanDefinition beanDefinition) {
        // 這裡可以新增監聽器
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public Object getBean(String beanName) {
        Object bean = beanMap.get(beanName);
        if(ObjectUtil.isNotNull(bean)) {
            // 這裡直接返回的是單例,如果使用者指定為多例,則每次都需要新建。
            return bean;
        }

        // 獲取對應設定資訊
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(ObjectUtil.isNull(beanDefinition)) {
            throw new IocRuntimeException(beanName + " not exists in bean define.");
        }

        // 直接根據
        Object newBean = createBean(beanDefinition);
        // 這裡可以新增對應的監聽器
        beanMap.put(beanName, newBean);
        return newBean;
    }

    /**
     * 根據物件定義資訊建立物件
     * @param beanDefinition 物件定義資訊
     * @return 建立的物件資訊
     * @since 0.0.1
     */
    private Object createBean(final BeanDefinition beanDefinition) {
        String className = beanDefinition.getClassName();
        Class clazz = ClassUtils.getClass(className);
        return ClassUtils.newInstance(clazz);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getBean(String beanName, Class<T> tClass) {
        Object object = getBean(beanName);
        return (T)object;
    }

}

其中 ClassUtils 是基於 class 的反射工具類

JsonApplicationContext

基於 json 組態檔實現的基本實現,使用方式見開始種的例子程式碼。

  • JsonApplicationContext.java
/**
 * JSON 應用上下文
 * @author binbin.hou
 * @since 0.0.1
 */
public class JsonApplicationContext extends DefaultBeanFactory {

    /**
     * 檔名稱
     * @since 0.0.1
     */
    private final String fileName;

    public JsonApplicationContext(String fileName) {
        this.fileName = fileName;

        // 初始化設定
        this.init();
    }

    /**
     * 初始化設定相關資訊
     *
     * <pre>
     *  new TypeReference<List<BeanDefinition>>(){}
     * </pre>
     *
     * 讀取檔案:https://blog.csdn.net/feeltouch/article/details/83796764
     * @since 0.0.1
     */
    private void init() {
        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        final String jsonConfig = FileUtil.getFileContent(is);
        List<DefaultBeanDefinition> beanDefinitions = JsonBs.deserializeArray(jsonConfig, DefaultBeanDefinition.class);
        if(CollectionUtil.isNotEmpty(beanDefinitions)) {
            for (BeanDefinition beanDefinition : beanDefinitions) {
                super.registerBeanDefinition(beanDefinition.getName(), beanDefinition);
            }
        }
    }

}

小結

至此,一個最基本的 spring ioc 就基本實現了。

學海無涯,我們一起勉力前行!

Ps:有需要的小夥伴可以點選直接進入:暗號:csdn,免費獲取。

面試專題檔案。

在這裡插入圖片描述

技術檔案

在這裡插入圖片描述

真實大廠面經

在這裡插入圖片描述