天天看點

《Spring 手撸專欄》第 2 章:小試牛刀(讓新手能懂),實作一個簡單的Bean容器

《Spring 手撸專欄》第 2 章:小試牛刀(讓新手能懂),實作一個簡單的Bean容器

作者:小傅哥

部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收獲!😄

一、前言

上學時,老師總說:不會你就問,但多數時候都不知道要問什麼!

你總會在小傅哥的文章前言裡,發現一些關于成長、學習、感悟以及對當篇内容的一個介紹,其實之是以寫這樣的鋪墊性内容,主要是為了讓大家對接下來的内容學習有一個較輕松的開場和過度。

就像我們上學時如果某一科的内容不會時,老師經常會說,你有不會的就要問。但對于學生本身來講,可能已經不會的太多了,或者壓根不知道自己不會什麼,隻有等看到老師出完的試卷才發現自己什麼都不會。但要是讓問,又不知道從哪問,問出蘿蔔帶出泥,到處都是知識漏洞。

是以我希望用一些前置内容的鋪墊,讓大家可以在一個稍有共識的場景下進行學習,或多或少能為你鋪墊出一個稍許平緩的接受期。有可能某些時候也會打打雞血、刺激刺激學習、總歸把知識學到手就是好的!

二、目标

Spring Bean 容器是什麼?

Spring 包含并管理應用對象的配置和生命周期,在這個意義上它是一種用于承載對象的容器,你可以配置你的每個 Bean 對象是如何被建立的,這些 Bean 可以建立一個單獨的執行個體或者每次需要時都生成一個新的執行個體,以及它們是如何互相關聯建構和使用的。

如果一個 Bean 對象交給 Spring 容器管理,那麼這個 Bean 對象就應該以類似零件的方式被拆解後存放到 Bean 的定義中,這樣相當于一種把對象解耦的操作,可以由 Spring 更加容易的管理,就像處理循環依賴等操作。

當一個 Bean 對象被定義存放以後,再由 Spring 統一進行裝配,這個過程包括 Bean 的初始化、屬性填充等,最終我們就可以完整的使用一個 Bean 執行個體化後的對象了。

而我們本章節的案例目标就是定義一個簡單的 Spring 容器,用于定義、存放和擷取 Bean 對象。

三、設計

凡是可以存放資料的具體資料結構實作,都可以稱之為容器。例如:ArrayList、LinkedList、HashSet等,但在 Spring Bean 容器的場景下,我們需要一種可以用于存放和名稱索引式的資料結構,是以選擇 HashMap 是最合适不過的。

這裡簡單介紹一下 HashMap,HashMap 是一種基于擾動函數、負載因子、紅黑樹轉換等技術内容,形成的拉鍊尋址的資料結構,它能讓資料更加散列的分布在哈希桶以及碰撞時形成的連結清單和紅黑樹上。它的資料結構會盡可能最大限度的讓整個資料讀取的複雜度在 O(1) ~ O(Logn) ~O(n)之間,當然在極端情況下也會有 O(n) 連結清單查找資料較多的情況。不過我們經過10萬資料的擾動函數再尋址驗證測試,資料會均勻的散列在各個哈希桶索引上,是以 HashMap 非常适合用在 Spring Bean 的容器實作上。

另外一個簡單的 Spring Bean 容器實作,還需 Bean 的定義、注冊、擷取三個基本步驟,簡化設計如下;

《Spring 手撸專欄》第 2 章:小試牛刀(讓新手能懂),實作一個簡單的Bean容器
  • 定義:BeanDefinition,可能這是你在查閱 Spring 源碼時經常看到的一個類,例如它會包括 singleton、prototype、BeanClassName 等。但目前我們初步實作會更加簡單的處理,隻定義一個 Object 類型用于存放對象。
  • 注冊:這個過程就相當于我們把資料存放到 HashMap 中,隻不過現在 HashMap 存放的是定義了的 Bean 的對象資訊。
  • 擷取:最後就是擷取對象,Bean 的名字就是key,Spring 容器初始化好 Bean 以後,就可以直接擷取了。

接下來我們就按照這個設計,做一個簡單的 Spring Bean 容器代碼實作。編碼的過程往往并不會有多複雜,但知曉設計過程卻更加重要!

四、實作

1. 工程結構

small-spring-step-01
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── BeanDefinition.java
    │           └── BeanFactory.java
    └── test
        └── java
            └── cn.bugstack.springframework.test  
                ├── bean
                │   └── UserService.java                
                └── ApiTest.java
           

工程源碼:https://github.com/small-spring/small-spring-step-01

Spring Bean 容器類關系,如圖 2-2

《Spring 手撸專欄》第 2 章:小試牛刀(讓新手能懂),實作一個簡單的Bean容器

Spring Bean 容器的整個實作内容非常簡單,也僅僅是包括了一個簡單的 BeanFactory 和 BeanDefinition,這裡的類名稱是與 Spring 源碼中一緻,隻不過現在的類實作會相對來說更簡化一些,在後續的實作過程中再不斷的添加内容。

  1. BeanDefinition,用于定義 Bean 執行個體化資訊,現在的實作是以一個 Object 存放對象
  2. BeanFactory,代表了 Bean 對象的工廠,可以存放 Bean 定義到 Map 中以及擷取。

2. Bean 定義

cn.bugstack.springframework.BeanDefinition

public class BeanDefinition {

    private Object bean;

    public BeanDefinition(Object bean) {
        this.bean = bean;
    }

    public Object getBean() {
        return bean;
    }

}
           
  • 目前的 Bean 定義中,隻有一個 Object 用于存放 Bean 對象。如果感興趣可以參考 Spring 源碼中這個類的資訊,名稱都是一樣的。
  • 不過在後面陸續的實作中會逐漸完善 BeanDefinition 相關屬性的填充,例如:SCOPE_SINGLETON、SCOPE_PROTOTYPE、ROLE_APPLICATION、ROLE_SUPPORT、ROLE_INFRASTRUCTURE 以及 Bean Class 資訊。

3. Bean 工廠

cn.bugstack.springframework.BeanFactory

public class BeanFactory {

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public Object getBean(String name) {
        return beanDefinitionMap.get(name).getBean();
    }

    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(name, beanDefinition);
    }

}
           
  • 在 Bean 工廠的實作中,包括了 Bean 的注冊,這裡注冊的是 Bean 的定義資訊。同時在這個類中還包括了擷取 Bean 的操作。
  • 目前的 BeanFactory 仍然是非常簡化的實作,但這種簡化的實作内容也是整個 Spring 容器中關于 Bean 使用的最終展現結果,隻不過實作過程隻展示出基本的核心原理。在後續的補充實作中,這個會不斷變得龐大。

五、測試

1. 事先準備

cn.bugstack.springframework.test.bean.UserService

public class UserService {

    public void queryUserInfo(){
        System.out.println("查詢使用者資訊");
    }

}
           
  • 這裡簡單定義了一個 UserService 對象,友善我們後續對 Spring 容器測試。

2. 測試用例

cn.bugstack.springframework.test.ApiTest

@Test
public void test_BeanFactory(){
    // 1.初始化 BeanFactory
    BeanFactory beanFactory = new BeanFactory();
    
    // 2.注冊 bean
    BeanDefinition beanDefinition = new BeanDefinition(new UserService());
    beanFactory.registerBeanDefinition("userService", beanDefinition);
    
    // 3.擷取 bean
    UserService userService = (UserService) beanFactory.getBean("userService");
    userService.queryUserInfo();
}
           
  • 在單測中主要包括初始化 Bean 工廠、注冊 Bean、擷取 Bean,三個步驟,使用效果上貼近與 Spring,但顯得會更簡化。
  • 在 Bean 的注冊中,這裡是直接把 UserService 執行個體化後作為入參傳遞給 BeanDefinition 的,在後續的陸續實作中,我們會把這部分内容放入 Bean 工廠中實作。

3. 測試結果

查詢使用者資訊

Process finished with exit code 0
           
  • 通過測試結果可以看到,目前的 Spring Bean 容器案例,已經稍有雛形。

六、總結

  • 整篇關于 Spring Bean 容器的一個雛形就已經實作完成了,相對來說這部分代碼并不會難住任何人,隻要你稍加嘗試就可以接受這部分内容的實作。
  • 但對于一個知識的學習來說,寫代碼隻是最後的步驟,往往整個思路、設計、方案,才更重要,隻要你知道了因為什麼、是以什麼,才能讓你有一個真正的了解。
  • 下一章節會在此工程基礎上擴容實作,要比現在的類多一些。不過每一篇的實作上,我都會以一個需求視角進行目标分析和方案設計,讓大家在學習編碼之外更能注重更多技術價值的學習。

七、系列推薦

  • 畢業前寫了20萬行代碼,讓我從成為同學眼裡的面霸!
  • HashMap核心知識,擾動函數、負載因子、擴容連結清單拆分,深度學習
  • HashMap資料插入、查找、删除、周遊,源碼分析
  • 看圖說話,講解2-3平衡樹「紅黑樹的前身」
  • 《SpringBoot 中間件設計和開發》| 對,小傅哥這次教你造火箭!

公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!