解密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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...