beanFactory 設計模式 Bean 生命周期的胡言亂語,哈哈

来源:https://www.cnblogs.com/sanri1993/archive/2019/11/07/11816411.html
-Advertisement-
Play Games

寫在前面的話 適用讀者:有一定經驗的,本文不適合初學者,因為可能不能理解我在說什麼 文章思路:不會一開始就像別的博客文章那樣,Bean 的生命周期,源碼解讀(給你貼一大堆的源碼)。個人覺得應該由問題驅動,為什麼為出現 BeanFactory ,為什麼會有生命周期。 正文 一開始我們使用 bean 都 ...


寫在前面的話

適用讀者:有一定經驗的,本文不適合初學者,因為可能不能理解我在說什麼

文章思路:不會一開始就像別的博客文章那樣,Bean 的生命周期,源碼解讀(給你貼一大堆的源碼)。個人覺得應該由問題驅動,為什麼為出現 BeanFactory ,為什麼會有生命周期。

正文

一開始我們使用 bean 都是簡單bean,如 vo ,po,entity,dto,我們是這麼玩的

XXEntity xxEntity = new XXEntity();
xxEntity.setPropA("字元串");

後面可能出現了某個比較複雜的 bean ,它有一個對象做為屬性,需要在構造時或構造後設置值(示例而已,不要較真),如

// 構建序列化實例,這裡 Serializable 是介面,使用介面的好處是在使用別的序列化時,不需要修改 jedis 類
Serializable fastJsonSerizlizable = new FastJsonSerizlizable();

// 構建目標 jedis 實例 ,需要先構建序列化對象 
Jedis jedis = new Jedis();
jedis.setSerializable(fastJsonSerizlizable);

這時來了 serviceA 類和 serviceB 類,它們都需要使用 redis,我不可能在每個類裡面都去把 jedis 實例化的過程寫一遍,這時有經驗的同學會寫一個工具類來創建 jedis ,像這樣

public BeanUtil {
    // 可以把創建序列化單拿出來,因為除了 redis 需要序列化之外,kafka 也需要序列化
    public static Serializable createSerializable(){
        return new FastJsonSerizlizable();
    }
    
    public static Jedis createJedis(){
        Jedis jedis = new Jedis();
        jedis.setSerializable(createSerializable());
        return jedis;
    }
}

// 這裡我 serviceA,serviceB 都可以使用 createJedis 來直接獲取 jedis 實例 ,而不需要關心創建細節,使用哪個序列化等問題

上面代碼有幾個問題

  • 每次使用時都會創建 jedis 對象 ,而每一個 jedis 對象又會單獨對一個 Serializable 對象 ,但是 fastJson 的序列化和 jedis 都只是工具類型的東西,一個實例足已。
  • 無法對 Jedis 進行配置
  • 不能讓使用者去創建 BeanUtil 實例 ,改進的代碼 如下
public BeanUtil {
    // 禁用 BeanUtil 構建 
    private BeanUtil(){}
    
    // 這裡我們可以使用 bean 的全路徑 => bean 實例來緩存 bean 
    static Map<String,Object> beansCache = new ConcurrentHashMap<String,Object>();
    
    static{
        // 初始化時,在內容緩存這些 bean 的實例,因為 jedis 依賴於 serializable ,需要需要先創建 serializable
        Serializable serializable = createSerializable();
        beansCache.put(Serializable.class.getSimpleName(),serializable);
        Jedis jedis = createJedis();
        beansCache.put(jedis.class.getSimpleName(),jedis);
    }
    
    static Serializable createSerializable(String type){
        Serializable serializable =  beansCache.get("serializable");
        if(serializable != null)return serializable;
        
        switch(type){
            case "kryo":    // kryo 不能用單例,請忽略本問題,示例而已
                return new KryoSerializable();
            case "protostuff":
                return new protostuffSerializable();
            default:
                return new FastJsonSerizlizable();
        }
    }
    
    static Jedis createJedis(String serializableType){
        Jedis jedis = new Jedis();
        Serializable serializable = beansCache.get("serializable");
        jedis.setSerializable(serializable);
        return jedis;
    }

    //然後對外提供獲取 Bean 的方法 
    public static Object getBean(String beanName){
        return beansCache.get(beanName);
    }
    
    public static T getBean(Class<T> type){
        return beansCache.get(type.getSimpleName());
    }

}

但如果寫這個類的是小明,經過一段時間後這個類里會出現大量的 createXx 和 XX 的初始化操作,而且依賴程度也非常複雜,這時小明想,是時候優化一波了,於是小明想了一種解決方案,定義了一種 xml 語法

使用 bean 標簽來定義一個 bean,每個 bean 都有唯一的一個 id 信息 ,使用 property 來定義它的屬性 ,如果是複雜屬性使用 ref ,解析這個xml 得到一個完整的 bean 依賴圖

<beans>
    <bean id="serializable" class="com.xx.FastJsonSerizlizable" />
    
    <bean id="jedis" class="com.xx.Jedis">
        <property name="host" value="localhost" />
        <property name="serializable" ref="serializable"/>
    </bean>
</beans>

這時會有一個依賴問題,我創建 jedis 要先創建 serializable ,但是 serializable 的 xml bean 定義是寫在文件前面 的,小明想了一個辦法,先把 ref 使用字元串先存著,全部放到一個 bean 定義中,像這樣

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

然後把其解析成一顆依賴樹,這樣就可以先構造樹葉,然後逐層構造對象 ,但也有一種棘手的情況 ,那就是迴圈依賴

root

  |-jedis

    |- serializable

什麼是迴圈依賴呢,最簡單的 A 依賴於 B,B 依賴於 A ,或者中間有更多的依賴最後形成了一個圈,A-B-C-A

最原始的解決方式是這樣的,我們可以先使用構造函數把它們都創建出來,不能是有帶它們的構造函數,然後通過 set 把對象通過屬性設置值。所以除了構造註入外,通過屬性方式是可以解決迴圈依賴的。

這時我們的 BeanUtil 變成了這樣,想想不能叫工具類了,改為實體類 Factory

public BeanFactory {
    
    Map<String,BeanDefinition> beanDefinitions = new HashMap();
    
    // 這裡我們可以使用 bean 的全路徑 => bean 實例來緩存 bean 
    Map<String,Object> beansCache = new ConcurrentHashMap<String,Object>();
 
    {
        // 載入 xml bean 配置文件
        beanDefinitions = loadXml(contextConfigurations:String []);
        
        //實例化所有 bean 
        beansCache = instanceBeans(beanDefinitions);
    }
    
    //然後對外提供獲取 Bean 的方法 
    public  Object getBean(String beanName){
        return beansCache.get(beanName);
    }
    
    public  T getBean(Class<T> type){
        return beansCache.get(type.getSimpleName());
    }
}

這看起來已經足夠完美了,但這時程式員A提問了,我需要對我的某個類的初始化時,我要獲取一些比如連接資源,文件資源,然後在類銷毀時想要回收資源,但根據上面沒任何辦法可以做到。

小明說,這好辦,我提供幾個介面給你,你實現一下,我會在實例化 Bean 的時候 ,如果發現你有實現介面,在相應的過程里我就幫你調用一下,於是小明就添加了兩個介面

public interface InitializingBean{
    void afterPropertiesSet() throws Exception;
}

public  interface DisposableBean{
    void destroy() throws Exception;
}

程式員A 的問題解決了,這時程式員B說,有沒有一種辦法,可以對所有 Bean 的初始化過程進行攔截,而不是我當前這個類,我想把每一個 service 改成代理類,我想要給 service 中的方法添加事務。

小明說,那好吧,我把 bean 的屬性都註入完了,然後給這個 bean 交給你,你裝飾一下這個 bean 然後再還給我,於是小明提供出了這樣一個介面 ,在 bean 初始化前和初始化後,你都可以來修改 bean ,不要要註意,這個是針對全局的,不是你個人的 bean ,要做好過濾操作

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException ;
    
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
}

程式員C 這時又發問了,我創建了一個 BeanA 但我怎麼樣可以拿到 BeanC 啊,我想看看 c 的一些屬性。

小說說,真煩,我乾脆把 map 都給你好,不,我把 BeanFactory 都給你好了,於是有了這個介面

public interface BeanFactoryAware{
    void setBeanFactory(BeanFactory beanUtil);
}

這時程式D 又問了,我在 setBeanFactory 的時候 ,我創建的全局 processor 執行了嗎,還是在之後執行,頭大。

小明說,我整理下執行順序,取個名吧,叫 bean 的生命周期,順便再提供幾個實用的介面,bean 的名字我還沒告訴你呢,於是整理的生命周期如下

反射創建 Bean 
填充對象屬性
BeanNameAware.setBeanName();
BeanFactoryAware.setBeanFactory ();
BeanPostProcessor.postProcessBeforeInitialization(); 多個
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization(); 多個
DisposableBean.destory()

程式員E 又說了,xml 配置太麻煩了,jdk1.5 不是有註解嗎,我在類上加個標識,你掃描我的類,幫我創建實例唄

然後我需要用的時候,我在屬性上加個標識,你同樣可以根據類型找到依賴的類,然後把對應的實例創建好,幫我把值放進去就好了,如果這個類的創建過程比較複雜,我自己來創建,然後我把它返回給你,我定義一個方法,加個 Bean 的標識,你來讀進容器。

於是小明又加了 @Component 來表示組件,@Bean 來表示自定義實例創建,@Autowired 來註入對象 @PostConstruct 來執行類的初始化工作 @PreDestroy 來做類的銷毀工作,類的生命周期變成這樣

反射創建 Bean 
填充對象屬性
BeanNameAware.setBeanName();
BeanFactoryAware.setBeanFactory ();
BeanPostProcessor.postProcessBeforeInitialization(); 多個
PostConstruct
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization(); 多個
PreDestroy
DisposableBean.destory()

但是為了相容以前的 xml 形式,小明這時把 BeanFactory 抽象成介面,提供 getBean 方法,根據職責單一原則,BeanFactory 不應該再做解析 Bean 的工作;

再創建一個介面用於載入 Bean 定義,有兩個實現 XmlBeanRegistry ,AnnotationBeanRegistry ,載入 Bean 定義後再合併,考慮到以後還有可能添加別的註冊 bean 的方式 ,一次性提供一個對外的介面

public interface BeanFactoryPostProcessor{
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

你可以把你規則寫成的 bean 定義,實例化為我要求的 BeanDefinition 然後發給我就可以自定義實現把你自定義的 bean 添加到容器中了

一點小推廣

創作不易,希望可以支持下我的開源軟體,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。

Excel 通用導入導出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi

使用模板代碼 ,從資料庫生成代碼 ,及一些項目中經常可以用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python win32com模塊 合併文件夾內多個docx文件為一個docx #!/usr/bin/env python # -*- coding: utf-8 -*- from win32com.client import Dispatch import os,sys #import panda ...
  • set介面 java.util.Set 介面和 java.util.List 介面一樣,同樣繼承自 Collection 介面,它與 Collection 介面中的方 法基本一致,但是set介面中元素無序,並且不重覆 分類 1.HashSet集合 2.LinkedHashSet集合 HashSet集 ...
  • day one 演算法是充分利用解題環境所提供的基本操作,對輸入數據進行 逐步加工、變換和處理,從而達到解決問題的目的。 電腦的基本功能操作包括以下四個方面: 邏輯運算:與、或、非; 算術運算:加、減、乘、除; 數據比較:大於、小於、等於、不等於、大於等於、小於等於; 數據傳送:輸入、輸出、賦值。 ...
  • 1.首先是對vs2017這款軟體的使用 1.VS中的scanf()這個函數的使用問題 直到這次寫代碼我才知道VS中用scanf是會被警告的,VS中正規的類似於scanf()函數的輸入函數是scanf_s()只有使用這個函數你才不會報錯,它有三個參分別是數據類型,地址,最大存儲量, 還有兩種方法 第一 ...
  • 寶塔官方建議是純凈的系統,我使用docker運行一個ubuntu容器,模擬一個純凈的系統,這樣也不會影響到我的其他服務。 docker run --name baota -id -p 8888:8888 ubuntu docker exec -it baota bashapt-get updatea ...
  • 一個程式就是一個世界,有很多對象(變數) Golang 語言面向對象編程說明 1) Golang 也支持面向對象編程(OOP),但是和傳統的面向對象編程有區別,並不是純粹的面向對 象語言。所以我們說 Golang 支持面向對象編程特性是比較準確的。 2) Golang 沒有類(class),Go 語 ...
  • 雙11臨近的我發現自己真的很窮很窮很窮(重要的問題說三遍)…… 貧窮催人上進。於是我就尋思著在空閑時間自己搗鼓一下錢生錢的游戲是怎麼玩的,畢竟就算註定做韭菜也要做一根有知識有理想的韭菜。 第一個要玩的模型就是股票交易中的均線黃金交叉。 作為一個基礎的韭菜一定聽說過均線黃金交叉原則,也就是說當短期移動 ...
  • Python繪圖庫Turtle Turtle介紹 Turtle是Python內嵌的繪製線、圓以及其他形狀(包括文本)的圖形模塊。 一個Turtle實際上是一個對象,在導入Turtle模塊時,就創建了對象,然後,可以調用Turtle對象的各種方法完成不同的操作。 當創建一個Turtle對象時,它的位置 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...