《Spring 手擼專欄》第 3 章:初顯身手,運用設計模式,實現 Bean 的定義、註冊、獲取

小傅哥發表於2021-05-24


作者:小傅哥
部落格:https://bugstack.cn

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

一、前言

你是否能預見覆雜內容的設計問題?

講道理,無論產品功能是否複雜,都有很大一部分程式設計師會寫出一堆 if...else 來完成開發並順利上線。這主要是原因沒法預見當前的需求,發展是否長遠、流量是否龐大、迭代是否迅速,所以在被催促上線的情況,不寫 if...else 是不可能的!

那你說,既然 if...else 實現的這麼快,還考慮資料結構、演算法邏輯、設計模式、系統架構嗎?當然這基本要看你的專案在可預見下能活多久,如果一個專案至少存活一年,並且在這一年中又會不斷的的迭代。就像;你做了一個營銷優惠券系統,在各種條件下發放各種型別的券,如果在最開始沒有考慮好系統設計和架構模式,那麼當活動頻發流量暴增需求迭代下、最後你可能會掛在系統事故上!

我們在把系統設計的視角聚焦到具體程式碼實現上,你會有什麼手段來實現你想要的設計模式呢?其實編碼方式主要依託於:介面定義、類實現介面、抽象類實現介面、繼承類、繼承抽象類,而這些操作方式可以很好的隔離開每個類的基礎功能、通用功能和業務功能,當類的職責清晰後,你的整個設計也會變得容易擴充套件和迭代。

接下來在本章節繼續完善 Spring Bean 容器框架的功能開發,在這個開發過程中會用到較多的介面、類、抽象類,它們之間會有類的實現、類的繼承。可以仔細參考這部分內容的開發實現,雖然並不會很複雜,但這種設計思路是完全可以複用到我們自己的業務系統開發中的。

二、目標

在上一章節 《小試牛刀,實現一個簡單的Bean容器》 我們初步依照 Spring Bean 容器的概念,實現了一個粗糙版本的程式碼實現。那麼本章節我們需要結合已實現的 Spring Bean 容器進行功能完善,實現 Bean 容器關於 Bean 物件的註冊和獲取。

這一次我們把 Bean 的建立交給容器,而不是我們在呼叫時候傳遞一個例項化好的 Bean 物件,另外還需要考慮單例物件,在物件的二次獲取時是可以從記憶體中獲取物件的。此外不僅要實現功能還需要完善基礎容器框架的類結構體,否則將來就很難擴容進去其他的功能了。

三、設計

鑑於本章節的案例目標,我們需要將 Spring Bean 容器完善起來,首先非常重要的一點是在 Bean 註冊的時候只註冊一個類資訊,而不會直接把例項化資訊註冊到 Spring 容器中。那麼就需要修改 BeanDefinition 中的屬性 Object 為 Class,接下來在需要做的就是在獲取 Bean 物件時需要處理 Bean 物件的例項化操作以及判斷當前單例物件在容器中是否已經快取起來了。整體設計如圖 3-1

  • 首先我們需要定義 BeanFactory 這樣一個 Bean 工廠,提供 Bean 的獲取方法 getBean(String name),之後這個 Bean 工廠介面由抽象類 AbstractBeanFactory 實現。這樣使用模板模式的設計方式,可以統一收口通用核心方法的呼叫邏輯和標準定義,也就很好的控制了後續的實現者不用關心呼叫邏輯,按照統一方式執行。那麼類的繼承者只需要關心具體方法的邏輯實現即可。
  • 那麼在繼承抽象類 AbstractBeanFactory 後的 AbstractAutowireCapableBeanFactory 就可以實現相應的抽象方法了,因為 AbstractAutowireCapableBeanFactory 本身也是一個抽象類,所以它只會實現屬於自己的抽象方法,其他抽象方法由繼承 AbstractAutowireCapableBeanFactory 的類實現。這裡就體現了類實現過程中的各司其職,你只需要關心屬於你的內容,不是你的內容,不要參與。這一部分內容我們會在程式碼裡有具體的體現
  • 另外這裡還有塊非常重要的知識點,就是關於單例 SingletonBeanRegistry 的介面定義實現,而 DefaultSingletonBeanRegistry 對介面實現後,會被抽象類 AbstractBeanFactory 繼承。現在 AbstractBeanFactory 就是一個非常完整且強大的抽象類了,也能非常好的體現出它對模板模式的抽象定義。接下來我們就帶著這些設計層面的思考,去看程式碼的具體實現結果

四、實現

1. 工程結構

small-spring-step-02
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework.beans
    │           ├── factory
    │           │   ├── factory
    │           │   │   ├── BeanDefinition.java
    │           │   │   └── SingletonBeanRegistry.java
    │           │   ├── support
    │           │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   ├── AbstractBeanFactory.java
    │           │   │   ├── BeanDefinitionRegistry.java
    │           │   │   ├── DefaultListableBeanFactory.java
    │           │   │   └── DefaultSingletonBeanRegistry.java
    │           │   └── BeanFactory.java
    │           └── BeansException.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   └── UserService.java
                └── ApiTest.java

工程原始碼公眾號「bugstack蟲洞棧」,回覆:Spring 專欄,獲取原始碼

Spring Bean 容器類關係,如圖 3-2

圖 3-2

雖然這一章節關於 Spring Bean 容器的功能實現與 Spring 原始碼中還有不少的差距,但以目前實現結果的類關係圖來看,其實已經具備了一定的設計複雜性,這些複雜的類關係設計在各個介面定義和實現以及在抽象類繼承中都有所體現,例如:

  • BeanFactory 的定義由 AbstractBeanFactory 抽象類實現介面的 getBean 方法
  • 而 AbstractBeanFactory 又繼承了實現了 SingletonBeanRegistry 的DefaultSingletonBeanRegistry 類。這樣 AbstractBeanFactory 抽象類就具備了單例 Bean 的註冊功能。
  • AbstractBeanFactory 中又定義了兩個抽象方法:getBeanDefinition(String beanName)、createBean(String beanName, BeanDefinition beanDefinition) ,而這兩個抽象方法分別由 DefaultListableBeanFactory、AbstractAutowireCapableBeanFactory 實現。
  • 最終 DefaultListableBeanFactory 還會繼承抽象類 AbstractAutowireCapableBeanFactory 也就可以呼叫抽象類中的 createBean 方法了。

綜上這一部分的類關係和實現過程還是會有一些複雜的,因為所有的實現都以職責劃分、共性分離以及呼叫關係定義為標準搭建的類關係。這部分內容的學習,可能會豐富你在複雜業務系統開發中的設計思路。

2. BeanDefinition 定義

cn.bugstack.springframework.beans.factory.config.BeanDefinition

public class BeanDefinition {

    private Class beanClass;

    public BeanDefinition(Class beanClass) {
        this.beanClass = beanClass;
    }
		// ...get/set
}
  • 在 Bean 定義類中已經把上一章節中的 Object bean 替換為 Class,這樣就可以把 Bean 的例項化操作放到容器中處理了。如果你有仔細閱讀過上一章並做了相應的測試,那麼你會發現 Bean 的例項化操作是放在初始化呼叫階段傳遞給 BeanDefinition 建構函式的。

3. 單例註冊介面定義和實現

cn.bugstack.springframework.beans.factory.config.SingletonBeanRegistry

public interface SingletonBeanRegistry {

    Object getSingleton(String beanName);

}
  • 這個類比較簡單主要是定義了一個獲取單例物件的介面。

cn.bugstack.springframework.beans.factory.config.DefaultSingletonBeanRegistry

public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    private Map<String, Object> singletonObjects = new HashMap<>();

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

    protected void addSingleton(String beanName, Object singletonObject) {
        singletonObjects.put(beanName, singletonObject);
    }

}
  • 在 DefaultSingletonBeanRegistry 中主要實現 getSingleton 方法,同時實現了一個受保護的 addSingleton 方法,這個方法可以被繼承此類的其他類呼叫。包括:AbstractBeanFactory 以及繼承的 DefaultListableBeanFactory 呼叫。

4. 抽象類定義模板方法(AbstractBeanFactory)

cn.bugstack.springframework.beans.factory.support.AbstractBeanFactory

public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

    @Override
    public Object getBean(String name) throws BeansException {
        Object bean = getSingleton(name);
        if (bean != null) {
            return bean;
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        return createBean(name, beanDefinition);
    }

    protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;

    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException;

}
  • AbstractBeanFactory 首先繼承了 DefaultSingletonBeanRegistry,也就具備了使用單例註冊類方法。
  • 接下來很重要的一點是關於介面 BeanFactory 的實現,在方法 getBean 的實現過程中可以看到,主要是對單例 Bean 物件的獲取以及在獲取不到時需要拿到 Bean 的定義做相應
    Bean 例項化操作。那麼 getBean 並沒有自身的去實現這些方法,而是隻定義了呼叫過程以及提供了抽象方法,由實現此抽象類的其他類做相應實現。
  • 後續繼承抽象類 AbstractBeanFactory 的類有兩個,包括:AbstractAutowireCapableBeanFactory、DefaultListableBeanFactory,這兩個類分別做了相應的實現處理,接著往下看。

5. 例項化Bean類(AbstractAutowireCapableBeanFactory)

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException {
        Object bean = null;
        try {
            bean = beanDefinition.getBeanClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        addSingleton(beanName, bean);
        return bean;
    }

}
  • 在 AbstractAutowireCapableBeanFactory 類中實現了 Bean 的例項化操作 newInstance,其實這塊會埋下一個坑,有建構函式入參的物件怎麼處理?可以提前思考
  • 在處理完 Bean 物件的例項化後,直接呼叫 addSingleton 方法存放到單例物件的快取中去。

6. 核心類實現(DefaultSingletonBeanRegistry)

cn.bugstack.springframework.beans.factory.support.DefaultSingletonBeanRegistry

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {

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

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws BeansException {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) throw new BeansException("No bean named '" + beanName + "' is defined");
        return beanDefinition;
    }

}
  • DefaultListableBeanFactory 在 Spring 原始碼中也是一個非常核心的類,在我們目前的實現中也是逐步貼近於原始碼,與原始碼類名保持一致。
  • DefaultListableBeanFactory 繼承了 AbstractAutowireCapableBeanFactory 類,也就具備了介面 BeanFactory 和 AbstractBeanFactory 等一連串的功能實現。所以有時候你會看到一些類的強轉,呼叫某些方法,也是因為你強轉的類實現介面或繼承了某些類。
  • 除此之外這個類還實現了介面 BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,當然你還會看到一個 getBeanDefinition 的實現,這個方法我們文中提到過它是抽象類 AbstractBeanFactory 中定義的抽象方法。現在註冊Bean定義與獲取Bean定義就可以同時使用了,是不感覺這個套路還蠻深的。介面定義了註冊,抽象類定義了獲取,都集中在 DefaultListableBeanFactory 中的 beanDefinitionMap 裡

五、測試

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
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    // 2.註冊 bean
    BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
    beanFactory.registerBeanDefinition("userService", beanDefinition);
    // 3.第一次獲取 bean
    UserService userService = (UserService) beanFactory.getBean("userService");
    userService.queryUserInfo();
    // 4.第二次獲取 bean from Singleton
    UserService userService_singleton = (UserService) beanFactory.getBean("userService");
    userService_singleton.queryUserInfo();
}
  • 在此次的單元測試中除了包括;Bean 工廠、註冊 Bean、獲取 Bean,三個步驟,還額外增加了一次物件的獲取和呼叫。這裡主要測試驗證單例物件的是否正確的存放到了快取中。
  • 此外與上一章節測試過程中不同的是,我們把 UserService.class 傳遞給了 BeanDefinition 而不是像上一章節那樣直接 new UserService() 操作。

3. 測試結果

查詢使用者資訊
查詢使用者資訊

Process finished with exit code 0
  • 這裡會有兩次測試資訊,一次是獲取 Bean 時直接建立的物件,另外一次是從快取中獲取的例項化物件。
  • 此外從除錯的截圖中也可以看到第二次獲取單例物件,已經可以從記憶體中獲取了,如圖 3-3
    圖 3-3
  • 到這本章節的功能實現和測試驗證就完成了,關於測試過程中可以再去斷點除錯下各個階段類的呼叫,熟悉呼叫關係。

六、總結

  • 相對於前一章節對 Spring Bean 容器的簡單概念實現,本章節中加強了功能的完善。在實現的過程中也可以看到類的關係變得越來越多了,如果沒有做過一些稍微複雜的系統類系統,那麼即使現在這樣9個類搭出來的容器工廠也可以給你繞暈。
  • 在 Spring Bean 容器的實現類中要重點關注類之間的職責和關係,幾乎所有的程式功能設計都離不開介面、抽象類、實現、繼承,而這些不同特性類的使用就可以非常好的隔離開類的功能職責和作用範圍。而這樣的知識點也是在學習手寫 Spring Bean 容器框架過程非常重要的知識。
  • 最後要強調一下關於整個系列內容的學習,可能在學習的過程中會遇到像第二章節那樣非常簡單的程式碼實現,但要做一個有成長的程式設計師要記住程式碼實現只是最後的落地結果,而那些設計上的思考才是最有價值的地方。就像你是否遇到過,有人讓你給一個內容做個描述、文件、說明,你總覺得太簡單了沒什麼可寫的,即使要動筆寫了也不知道要從哪開始!其實這些知識內容都來源你對整體功能的理解,這就不只是程式碼開發還包括了需求目標、方案設計、技術實現、邏輯驗證等等過程性的內容。所以,不要只是被看似簡單的內容忽略了整體全域性觀,要學會放開視野,開放學習視角。

七、系列推薦

相關文章