解密Spring中的Bean實例化:推斷構造方法(上)

来源:https://www.cnblogs.com/guoxiaoyu/p/18045706
-Advertisement-
Play Games

在Spring中,實例化Bean對象涉及構造方法的調用。通過分析源碼,我們瞭解到實例化的步驟和推斷構造方法的過程。當一個類只有一個構造方法時,Spring會根據具體情況決定是否使用該構造方法。如果一個類存在多個構造方法,就需要根據具體情況具體分析。 ...


在Spring中,一個bean需要通過實例化來獲取一個對象,而實例化的過程涉及到構造方法的調用。本文將主要探討簡單的構造推斷和實例化過程,讓我們首先深入瞭解實例化的步驟。

實例化源碼

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // Make sure bean class is actually resolved at this point.
    Class<?> beanClass = resolveBeanClass(mbd, beanName);

    .....

    // BeanDefinition中添加了Supplier,則調用Supplier來得到對象
    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
    if (instanceSupplier != null) {
        return obtainFromSupplier(instanceSupplier, beanName);
    }

    // @Bean對應的BeanDefinition
    if (mbd.getFactoryMethodName() != null) {
        return instantiateUsingFactoryMethod(beanName, mbd, args);
    }

    // Shortcut when re-creating the same bean...
    // 一個原型BeanDefinition,會多次來創建Bean,那麼就可以把該BeanDefinition所要使用的構造方法緩存起來,避免每次都進行構造方法推斷
    boolean resolved = false;
    boolean autowireNecessary = false;
    if (args == null) {
        synchronized (mbd.constructorArgumentLock) {
            if (mbd.resolvedConstructorOrFactoryMethod != null) {
                resolved = true;
                // autowireNecessary表示有沒有必要要進行註入,比如當前BeanDefinition用的是無參構造方法,那麼autowireNecessary為false,否則為true,表示需要給構造方法參數註入值
                autowireNecessary = mbd.constructorArgumentsResolved;
            }
        }
    }
    if (resolved) {
        // 如果確定了當前BeanDefinition的構造方法,那麼看是否需要進行對構造方法進行參數的依賴註入(構造方法註入)
        if (autowireNecessary) {
            // 方法內會拿到緩存好的構造方法的入參
            return autowireConstructor(beanName, mbd, null, null);
        }
        else {
            // 構造方法已經找到了,但是沒有參數,那就表示是無參,直接進行實例化
            return instantiateBean(beanName, mbd);
        }
    }

    // 如果沒有找過構造方法,那麼就開始找了

    // Candidate constructors for autowiring?
    // 提供一個擴展點,可以利用SmartInstantiationAwareBeanPostProcessor來控制用beanClass中的哪些構造方法
    // 比如AutowiredAnnotationBeanPostProcessor會把加了@Autowired註解的構造方法找出來,具體看代碼實現會更複雜一點
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);

    // 如果推斷出來了構造方法,則需要給構造方法賦值,也就是給構造方法參數賦值,也就是構造方法註入
    // 如果沒有推斷出來構造方法,但是autowiremode為AUTOWIRE_CONSTRUCTOR,則也可能需要給構造方法賦值,因為不確定是用無參的還是有參的構造方法
    // 如果通過BeanDefinition指定了構造方法參數值,那肯定就是要進行構造方法註入了
    // 如果調用getBean的時候傳入了構造方法參數值,那肯定就是要進行構造方法註入了
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }

    // Preferred constructors for default construction?
    ctors = mbd.getPreferredConstructors();
    if (ctors != null) {
        return autowireConstructor(beanName, mbd, ctors, null);
    }

    // No special handling: simply use no-arg constructor.
    // 不匹配以上情況,則直接使用無參構造方法
    return instantiateBean(beanName, mbd);
}

在Spring框架中的AbstractAutowireCapableBeanFactory類中的createBeanInstance()方法是用來執行實例化Bean對象的操作,該方法會根據Bean定義信息和配置選項,經過一系列步驟和邏輯判斷,最終創建一個全新的Bean實例。大致步驟如下:

  • 根據BeanDefinition載入類並獲取對應的Class對象
  • 如果BeanDefinition綁定了一個Supplier,那麼應調用Supplier的get方法以獲取一個對象,並將其直接返回。
  • 如果在BeanDefinition中存在factoryMethodName屬性,則調用該工廠方法以獲取一個bean對象,並將其返回。
  • 如果BeanDefinition已經自動構造過了,那麼就調用autowireConstructor()方法來自動構造一個對象。
  • 調用SmartInstantiationAwareBeanPostProcessor介面的determineCandidateConstructors()方法,以確定哪些構造方法是可用的。
  • 如果存在可用的構造方法,或者當前BeanDefinition的autowired屬性設置為AUTOWIRE_CONSTRUCTOR,或者BeanDefinition中指定了構造方法參數值,或者在創建Bean時指定了構造方法參數值,那麼將調用autowireConstructor()方法來自動構造一個對象。
  • 最後,如果不符合前述情況,那麼將根據不帶參數的構造方法來實例化一個對象。

推斷構造方法

我們看下源碼:

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
        throws BeanCreationException {
  //前面跟@Lookup相關,我們不看
    .....

    // Quick check on the concurrent map first, with minimal locking.
    Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
    if (candidateConstructors == null) {
        // Fully synchronized resolution now...
        synchronized (this.candidateConstructorsCache) {
            candidateConstructors = this.candidateConstructorsCache.get(beanClass);
            if (candidateConstructors == null) {
                Constructor<?>[] rawCandidates;
                try {
                    // 拿到所有的構造方法
                    rawCandidates = beanClass.getDeclaredConstructors();
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(beanName,
                            "Resolution of declared constructors on bean Class [" + beanClass.getName() +
                            "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
                }
                List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);

                // 用來記錄required為true的構造方法,一個類中只能有一個required為true的構造方法
                Constructor<?> requiredConstructor = null;
                // 用來記錄預設無參的構造方法
                Constructor<?> defaultConstructor = null;
                ......
                int nonSyntheticConstructors = 0;

                // 遍歷每個構造方法
                for (Constructor<?> candidate : rawCandidates) {
                    if (!candidate.isSynthetic()) {
                        // 記錄一下普通的構造方法
                        nonSyntheticConstructors++;
                    }
                    else if (primaryConstructor != null) {
                        continue;
                    }

                    // 當前遍歷的構造方法是否寫了@Autowired
                    MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
                    if (ann == null) {
                        // 如果beanClass是代理類,則得到被代理的類的類型,我們不看這種情況
                        .......
                    }

                    // 當前構造方法上加了@Autowired
                    if (ann != null) {
                        // 整個類中如果有一個required為true的構造方法,那就不能有其他的加了@Autowired的構造方法
                        if (requiredConstructor != null) {
                            throw new BeanCreationException(beanName,
                                    "Invalid autowire-marked constructor: " + candidate +
                                    ". Found constructor with 'required' Autowired annotation already: " +
                                    requiredConstructor);
                        }

                        boolean required = determineRequiredStatus(ann);
                        if (required) {
                            if (!candidates.isEmpty()) {
                                throw new BeanCreationException(beanName,
                                        "Invalid autowire-marked constructors: " + candidates +
                                        ". Found constructor with 'required' Autowired annotation: " +
                                        candidate);
                            }
                            // 記錄唯一一個required為true的構造方法
                            requiredConstructor = candidate;
                        }
                        // 記錄所有加了@Autowired的構造方法,不管required是true還是false
                        // 如果預設無參的構造方法上也加了@Autowired,那麼也會加到candidates中
                        candidates.add(candidate);

                        // 從上面代碼可以得到一個結論,在一個類中,要麼只能有一個required為true的構造方法,要麼只能有一個或多個required為false的方法
                    }
                    else if (candidate.getParameterCount() == 0) {
                        // 記錄唯一一個無參的構造方法
                        defaultConstructor = candidate;
                    }

                    // 有可能存在有參、並且沒有添加@Autowired的構造方法
                }


                if (!candidates.isEmpty()) {
                    // Add default constructor to list of optional constructors, as fallback.
                    // 如果不存在一個required為true的構造方法,則所有required為false的構造方法和無參構造方法都是合格的
                    if (requiredConstructor == null) {
                        if (defaultConstructor != null) {
                            candidates.add(defaultConstructor);
                        }
                        else if (candidates.size() == 1 && logger.isInfoEnabled()) {
                            logger.info("Inconsistent constructor declaration on bean with name '" + beanName +
                                    "': single autowire-marked constructor flagged as optional - " +
                                    "this constructor is effectively required since there is no " +
                                    "default constructor to fall back to: " + candidates.get(0));
                        }
                    }
                    // 如果只存在一個required為true的構造方法,那就只有這一個是合格的
                    candidateConstructors = candidates.toArray(new Constructor<?>[0]);
                }
                // 沒有添加了@Autowired註解的構造方法,並且類中只有一個構造方法,並且是有參的
                else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
                    candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
                }
                ......
                else {
                    // 如果有多個有參、並且沒有添加@Autowired的構造方法,是會返回空的
                    candidateConstructors = new Constructor<?>[0];
                }
                this.candidateConstructorsCache.put(beanClass, candidateConstructors);
            }
        }
    }
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

接下來,讓我們更深入地探討推斷構造方法,並且我還簡單繪製了一張圖示。

image

通常情況下,一個類通常只包含一個構造方法:

要麼是無參的構造方法,要麼是有參的構造方法。

如果一個類只有一個無參構造方法,那麼在實例化時只能使用這個構造方法;而如果一個類只有一個有參構造方法,實例化時是否可以使用這個構造方法則取決於具體情況:

  • 使用AnnotationConfigApplicationContext進行實例化時,Spring會根據構造方法的參數信息去尋找對應的bean,並將找到的bean傳遞給構造方法。這樣可以實現依賴註入,確保實例化的對象具有所需的依賴項。
  • 當使用ClassPathXmlApplicationContext時,表明使用XML配置文件的方式來定義bean。在XML中,可以手動指定構造方法的參數值來實例化對象,也可以通過配置autowire=constructor屬性,讓Spring自動根據構造方法的參數類型去尋找對應的bean作為參數值,實現自動裝配。

上面是只有一個構造方法的情況,那麼如果一個類存在多個構造方法,那麼Spring進行實例化之前,該如何去確定到底用哪個構造方法呢?

  • 如果開發者明確定義了他們想要使用的構造方法,那麼程式將會優先使用這個構造方法。
  • 如果開發者沒有明確指定他們想要使用的構造方法,系統會自動檢查開發者是否已經配置了讓Spring框架自動選擇構造方法的選項。
  • 如果開發者沒有顯式地指定讓Spring框架自動選擇構造方法的情況下,Spring將會預設嘗試使用無參構造方法。如果目標類中沒有無參構造方法,則系統將會拋出錯誤提示。

針對第一點,開發者可以通過什麼方式來指定使用哪個構造方法呢?

  • 在XML中,標簽用於表示構造方法的參數。開發者可以根據這個標簽確定所需使用的構造方法的參數個數,從而精確指定想要使用的構造方法。
  • 通過@Autowired註解,我們可以在構造方法上使用@Autowired註解。因此,當我們在特定構造方法上使用@Autowired註解時,表示開發者希望使用該構造方法。與通過xml方式直接指定構造方法參數值的方式不同,@Autowired註解方式需要Spring通過byType+byName的方式來查找符合條件的bean作為構造方法的參數值。
  • 當然,還有一種情況需要考慮,即當多個構造方法上都標註了@Autowired註解時,此時Spring會拋出錯誤。然而,由於@Autowired註解具有一個required屬性,預設值為true,因此在一個類中只能有一個構造方法標註了@Autowired或者@Autowired(required=true),否則會導致錯誤。不過,可以有多個構造方法標註@Autowired(required=false)。在這種情況下,Spring會自動從這些構造方法中選擇一個進行註入。

在第二種情況下,如果開發者沒有明確指定要使用的構造方法,Spring將嘗試自動選擇一個合適的構造方法進行註入。這種情況下,只能通過ClassPathXmlApplicationContext實現,因為使用AnnotationConfigApplicationContext無法指定讓Spring自動選擇構造方法。通過ClassPathXmlApplicationContext,可以在XML配置文件中指定某個bean的autowire屬性為constructor,從而告訴Spring可以自動選擇構造方法。

總結

在Spring中,實例化Bean對象涉及構造方法的調用。通過分析源碼,我們瞭解到實例化的步驟和推斷構造方法的過程。當一個類只有一個構造方法時,Spring會根據具體情況決定是否使用該構造方法。如果一個類存在多個構造方法,就需要根據具體情況具體分析。本文簡單判斷了哪些構造方法是符合實例化的,但在存在多個符合條件的構造方法時,具體使用哪個構造方法尚未討論。因此,我計劃單獨寫一篇文章來詳細討論這個問題。畢竟是太複雜了。


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

-Advertisement-
Play Games
更多相關文章
  • 1. 有人說 Python 性能沒那麼 Low? 這個我用 pypy 2.7 確認了下,確實沒那麼差, 如果用 NumPy 或其他版本 Python 的話,性能更快。但 pypy 還不完善,pypy3 在 beta, 所以一般情況,我是說一般情況下,這點比較讓人不爽。 2. 有人說怎麼沒有 C#、R ...
  • 1.創建 2.配置tomcat 3.創建webapp step01,war包 step02 創建web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xml ...
  • 3. Java程式流程式控制制(重點) 程式的三種控制結構 3.1 分支結構 if, switch 3.1.1 if if 分支 根據條件(真或假)來決定執行某段代碼。 if分支應用場景 if 第一種形式 執行流程: 首先判斷條件表達式的結果,如果為true執行語句體,為false就不執行語句體。 if ...
  • C-18.MySQL8其他新特性 1.MySQL8新特性概述 MySQL從5.7版本直接跳躍發佈了8.0版本,可見是一個令人興奮的里程碑的版本。MySQL 8版本在功能上,做了顯著的改進與增強,開發者對MySQL的源代碼進行了重構,最突出的一點是對MySQL Optimizer優化器進行了改進。不僅 ...
  • C++ MySQL資料庫連接池 新手學了C++多線程,看了些資料練手寫了C++資料庫連接池小項目,自己的源碼地址 關鍵技術點 MySQL資料庫編程、單例模式、queue隊列容器、C++11多線程編程、線程互斥、線程同步通信和 unique_lock、基於CAS的原子整形、智能指針shared_ptr ...
  • 數據過濾在數據分析過程中具有極其重要的地位,因為在真實世界的數據集中,往往存在重覆、缺失或異常的數據。pandas提供的數據過濾功能可以幫助我們輕鬆地識別和處理這些問題數據,從而確保數據的質量和準確性。 今天介紹的query函數,為我們提供了強大靈活的數據過濾方式,有助於從複雜的數據集中提取有價值的 ...
  • 目錄數組(Array)一、數組概念二、如何聲明一個數組三、如何為數組初始化1、數組本身初始化:2、數組的元素初始化2.1 一維數組2.2多維數組四、如何表示數組的各個概念五、數組記憶體和分配空間六、數組相關演算法七、十大內部排序演算法八、數組的工具類:Arrays九、數組的異常 數組(Array) 一、數 ...
  • 前言 在學習C++時,const關鍵字的知識點分散在書的各個章節。當我們嘗試在編程時使用const時,總會感覺有一些細節被遺忘,因而不能得心應手地使用const關鍵字。因此,本篇文章嘗試著對const關鍵字的做一些總結。參考書籍《C++ Primer Plus》 const總結 這裡是我做的關於co ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...