Spring源碼二十一:Bean實例化流程四

上一篇Spring源碼二十:Bean實例化流程三中,我們主要討論了單例Bean創建對象的主要方法getSingleton的內部方法createBean,createBean方法中的resolveBeanClase方法與prepareMethodOverrides方法處理了lookup-method屬性與repliace-method配置屬性。重點討論了resolveBeforeInstantiation,看了Spring為我們提供的兩個擴展方法。最后我們找到了正常創建bena的方法,doCreateBean。

今天開始進入doCreateBean中一探究竟:


doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.// 實例化這個beanBeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {// 如果是單例,則從factoryBeanInstanceCache獲取一個FactoryBeanWrapper對象,// 默認情況是單且factoryBeanInstanceCache為空,所以instanceWrapper還是 = nullinstanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}// 創建BeanWrapper實例對象:默認情況都是空,所以肯定會走這里if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}// 獲取實例化對象final Object bean = instanceWrapper.getWrappedInstance();// 獲取實例化對象類型Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// Allow post-processors to modify the merged bean definition.// 允許后置處理器修改beanDefinitionsynchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance. bean對象的初始化、DI在此出發// 這個exposed的對象,Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {throw (BeanCreationException) ex;}else {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);}}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] in its raw version as part of a circular reference, but has eventually been " +"wrapped. This means that said other beans do not use the final version of the " +"bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;}

首先回去BeanFactory的緩存中獲取Bean Wrapper對象,默認沒有所以肯定會走到createBeanInstance方法中,我們進入方法內部看下。

createBeanInstance

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// Make sure bean class is actually resolved at this point.// 在此調用resolveBeanClass方法,解析BeanDefinition中的class ,確保現在bean的class已經被解析好了Class<?> beanClass = resolveBeanClass(mbd, beanName);// 這里對class進行校驗,主要是反射需要用的到的屬性:// 如果類不是public修飾的,并且不能通過反射訪問 提前拋出異常if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());}Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}// 如果屬性factory-method不為空,則通過配置好的工廠方法來實例化bean// 這里為Spring 提供了通過工廠方法來實例化Beanif (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}// Shortcut when re-creating the same bean...boolean resolved = false;boolean autowireNecessary = false;// 參數為空時處理if (args == null) {synchronized (mbd.constructorArgumentLock) {if (mbd.resolvedConstructorOrFactoryMethod != null) {resolved = true;autowireNecessary = mbd.constructorArgumentsResolved;}}}if (resolved) {if (autowireNecessary) {return autowireConstructor(beanName, mbd, null, null);}else {return instantiateBean(beanName, mbd);}}// Candidate constructors for autowiring?Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);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);}

上述代碼注釋基本上都解釋了一遍,咱們簡單總結下:

類解析和校驗:首先解析bean的類,并進行訪問權限校驗。這一步確保了后續操作的前提條件都已經滿足。

實例供應商檢查:如果定義了實例供應商,則通過供應商創建實例。這種方式提供了高度的靈活性,使得實例的創建可以由外部邏輯控制。一般不做擴展,故不做具體分析

工廠方法實例化:如果定義了工廠方法,則通過工廠方法創建實例。工廠方法模式是一種常見的設計模式,能夠靈活地創建對象。這塊我們等會進去看下。主要還是Spring給我們提供了一種實例化方式,等會舉例說明。

構造函數解析和自動裝配:解析候選的構造函數并進行自動裝配。這種方式適用于需要依賴注入的場景,確保bean的依賴能夠被正確地解析和注入。

默認實例化:如果沒有特殊的創建需求,則使用無參構造函數進行實例化。這種方式簡單直接,適用于大多數情況。

這段代碼體現了Spring框架在設計和實現上的許多優秀思想和實踐。通過靈活的實例化策略、嚴格的前置校驗、線程安全處理、性能優化以及分層設計,Spring框架能夠高效、穩定地處理復雜的bean創建需求。這些設計思想和實踐對于任何大型軟件系統的設計和實現都有重要的借鑒意義。

工廠方法來實例化Bean

有兩種方式來定義,主要是靜態方法與普通方法,下面咱們來看下代碼:

package org.springframework.factory;import org.springframework.dto.JmUser;/*** @author Jeremy* @version 1.0* @description: 測試FactoryMethod方法* @date 2024/7/10 16:56*/
public class JmUserFactory {public static JmUser getObject(){JmUser jmUser = new JmUser();jmUser.setName("JmUserFactory");jmUser.setAge("1");return 	jmUser;}public JmUser getJmUser(){JmUser jmUser = new JmUser();jmUser.setName("JmUserFactoryNoStatic");jmUser.setAge("2");return 	jmUser;}}package org.springframework.dto;/*** @author Jeremy* @version 1.0* @description: 實體類* @date 2024/6/30 03:02*/
public class JmUser {private String name;private String age;private String systemOs;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}
}
<!--factory-method 靜態方法--><bean id="jmUserFactory" class="org.springframework.factory.JmUserFactory" factory-method="getObject"></bean><!--factory-method 非靜態方法--><bean id="jmUserFactoryNoStatic" class="org.springframework.factory.JmUserFactory" ></bean><bean id="jmCreateUser" factory-bean="jmUserFactoryNoStatic" factory-method="getJmUser"  ></bean>

可以看到此時會我們在xml中設置的factory-method方法會set到BeanDefinitionfactoryMethodName中,這里通過getFactoryMethodName方法獲取屬性值,如果有值則進入factoryMethod方法內部進行實例化。

instantiateUsingFactoryMethod

代碼比較長,我大家可以自己去代碼里看,我這里就截取核心內容出來。

Spring 框架中用于通過工廠方法實例化 bean 的方法。無論是 bean 類上的靜態工廠方法,還是其他工廠 bean 上的方法,都是通過這個方法來處理的。以下是該方法的概述和關鍵部分的解釋:

instantiateUsingFactoryMethod 方法旨在使用指定的工廠方法創建 bean 的新實例。它通過解析適當的方法、準備必要的參數,然后調用該方法來創建 bean。

BeanWrapper 初始化:


BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);

這里創建了一個 BeanWrapperImpl 實例并進行初始化,BeanWrapper 用于包裝 bean 對象并提供對其屬性的操作。

獲取工廠 bean 和工廠類:


String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {if (factoryBeanName.equals(beanName)) {throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,"factory-bean reference points back to the same bean definition");}factoryBean = this.beanFactory.getBean(factoryBeanName);if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {throw new ImplicitlyAppearedSingletonException();}factoryClass = factoryBean.getClass();isStatic = false;
}
else {if (!mbd.hasBeanClass()) {throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,"bean definition declares neither a bean class nor a factory-bean reference");}factoryBean = null;factoryClass = mbd.getBeanClass();isStatic = true;
}

這里獲取工廠 bean 和工廠類。如果指定了工廠 bean 名稱,則獲取該工廠 bean 實例及其類;如果沒有指定工廠 bean 名稱,則使用 bean 類上的靜態工廠方法。

解析工廠方法和參數:


Method factoryMethodToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;if (explicitArgs != null) {argsToUse = explicitArgs;
}
else {Object[] argsToResolve = null;synchronized (mbd.constructorArgumentLock) {factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {argsToUse = mbd.resolvedConstructorArguments;if (argsToUse == null) {argsToResolve = mbd.preparedConstructorArguments;}}}if (argsToResolve != null) {argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);}
}

這里解析工廠方法和參數。如果有顯式參數,則直接使用;否則,從 bean 定義中解析參數。

確定工廠方法:


if (factoryMethodToUse == null || argsToUse == null) {factoryClass = ClassUtils.getUserClass(factoryClass);List<Method> candidates = null;if (mbd.isFactoryMethodUnique) {if (factoryMethodToUse == null) {factoryMethodToUse = mbd.getResolvedFactoryMethod();}if (factoryMethodToUse != null) {candidates = Collections.singletonList(factoryMethodToUse);}}if (candidates == null) {candidates = new ArrayList<>();Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);for (Method candidate : rawCandidates) {if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {candidates.add(candidate);}}}...
}

這里嘗試確定具體的工廠方法。首先檢查是否有緩存的工廠方法,然后嘗試從工廠類的所有方法中找到匹配的方法。

實例化 bean:


bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
return bw;

最后,通過解析得到的工廠方法和參數來實例化 bean,并將其包裝在 BeanWrapper 中返回。

總結下:就是判斷一下當前是通過靜態工廠方法,還是通過實例工廠方法來實例化bean的實例。剩下復雜的錯都通過反射實例化bean的過程。

小結

今天咱們主要分析里createBeanInstance方法Spring給我們提供給的FactoryMethod方法,舉例說明了factoryMethod屬性如何使用,同時簡單討論了具體實現邏輯。下一節咱們將進入反射實例化Bean。

總結

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/43205.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/43205.shtml
英文地址,請注明出處:http://en.pswp.cn/web/43205.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MT3046 憤怒的象棚

思路&#xff1a; a[]存憤怒值&#xff1b;b[i]存以i結尾的&#xff0c;窗口里的最大值&#xff1b;c[i]存以i結尾的&#xff0c;窗口里面包含?的最大值。 &#xff08;?為新大象的位置&#xff09; 例&#xff1a;1 2 3 4 ? 5 6 7 8 9 則ans的計算公式b3b4c4c5c6b7b8b9…

三代測序結構變異分析 - 單樣本Germline SV calling和多樣本SV Calling

適用于三代PacBio HiFi / ONT 長reads數據的結構變異分析。 1. sniffles2安裝 sniffles2需要Python >= 3.10環境,因此用conda創建安裝好3.10的環境。 sniffles2安裝要求: Python >= 3.10pysam >= 0.21.0edlib >=1.3.9psutil>=5.9.4# 創建conda環境 conda c…

【記錄】LaTex|LaTex 代碼片段 Listings 添加帶圓圈數字標號的箭頭(又名 LaTex Tikz 庫畫箭頭的簡要介紹)

文章目錄 前言注意事項1 Tikz 的調用方法&#xff1a;newcommand2 標號圓圈數字的添加方式&#xff1a;\large{\textcircled{\small{1}}}\normalsize3 快速掌握 Tikz 箭頭寫法&#xff1a;插入點相對位移標號node3.1 第一張圖&#xff1a;插入點相對位移3.2 第二張圖&#xff1…

【MindSpore學習打卡】應用實踐-LLM原理和實踐-基于MindSpore實現BERT對話情緒識別

在當今的自然語言處理&#xff08;NLP&#xff09;領域&#xff0c;情緒識別是一個非常重要的應用場景。無論是在智能客服、社交媒體分析&#xff0c;還是在情感計算領域&#xff0c;準確地識別用戶的情緒都能夠極大地提升用戶體驗和系統的智能化水平。BERT&#xff08;Bidirec…

imx6ull/linux應用編程學習(12)CAN應用編程基礎

關于裸機的can通信&#xff0c;會在其他文章發&#xff0c;這里主要講講linux上的can通信。 與I2C,SPI等同步通訊方式不同&#xff0c;CAN通訊是異步通訊&#xff0c;也就是沒有時鐘信號線來保持信號接收同步&#xff0c;也就是所說的半雙工&#xff0c;無法同時發送與接收&…

【Java 注解,自定義注解,元注解,注解本質,注解解析】

文章目錄 什么是注解&#xff1f;Java內置注解自定義注解元注解注解的本質注解解析 什么是注解&#xff1f; 注解是Java編程語言中的一種元數據&#xff0c;提供了有關程序的額外信息。注解以符號開始&#xff0c;緊跟著注解的名稱和一對括號&#xff0c;括號內包含注解的參數…

C++基礎篇(1)

目錄 前言 1.第一個C程序 2.命名空間 2.1概念理解 2.2namespace 的價值 2.3 namespace的定義 3.命名空間的使用 4.C的輸入輸出 結束語 前言 本節我們將正式進入C基礎的學習&#xff0c;話不多說&#xff0c;直接上貨&#xff01;&#xff01;&#xff01; 1.第一個C程…

【Linux進階】文件系統8——硬鏈接和符號連接:ln

在Linux下面的鏈接文件有兩種&#xff0c; 一種是類似Windows的快捷方式功能的文件&#xff0c;可以讓你快速地鏈接到目標文件&#xff08;或目錄)&#xff1b;另一種則是通過文件系統的inode 鏈接來產生新文件名&#xff0c;而不是產生新文件&#xff0c;這種稱為硬鏈接&…

base SAS programming學習筆記10(combine data)

1.一對一合并 基本格式如下&#xff1a; data output-data-set; set data-set1; set data-set2;(data-set1和data-set2可以是相同的數據集&#xff0c;可以添加多個set 語句來實現上述的一對一合并) run; 輸出數據集結果如下&#xff1a; a.會包含所有輸入數據的變量名&#x…

小米手機永久刪除的照片怎么找回?這兩個方法千萬不要錯過!

小米手機永久刪除的照片怎么找回&#xff1f;身為米粉發燒黨的小編又雙叒叕手殘了&#xff01;本來想在手機回收站中恢復一張照片&#xff0c;結果一個稀里糊涂就把照片點成了“永久刪除”。于是乎難得的休班假期&#xff0c;就變成了小編恢復永久刪除照片的漫漫之路。以下是小…

org.springframework.boot.autoconfigure.EnableAutoConfiguration=XXXXX的作用是什么?

org.springframework.boot.autoconfigure.EnableAutoConfigurationXXXXXXX 這一配置項在 Spring Boot 項目中的作用如下&#xff1a; 自動配置類的指定&#xff1a; 這一配置將 EnableAutoConfiguration 設置為 cn.geek.javadatamanage.config.DataManageAutoConfiguration&…

【2024_CUMCM】TOPSIS法(優劣解距離法)

目錄 引入 層次分析法的局限性 簡介 例子 想法1 想法2 運用實際分數進行處理 想法3 問題 擴展問題&#xff1a;增加指標個數 極大型指標與極小型指標 統一指標類型-指標正向化 標準化處理 計算公式 計算得分 對原公式進行變化 升級到m個指標和n個對象 代碼 …

系統分析師-基礎知識

基礎知識 一、計算機組成與結構1、計算機系統基礎知識1.1 計算機硬件組成1.2 中央處理單元&#xff08;CPU&#xff09;1.3 數據表示1.3.1 R進制轉十進制&#xff1a;1.3.2 十進制轉R進制&#xff1a; 1.4 校驗碼&#xff08;3種校驗碼&#xff09;1.4.1 基本知識1.4.2 奇偶校驗…

D-DPCC: Deep Dynamic Point Cloud Compression via 3D Motion Prediction

1. 論文基本信息 發布于&#xff1a; 2022 2. 創新點 首先提出了一種端到端深度動態點云壓縮框架(D-DPCC)&#xff0c;用于運動估計、運動補償、運動壓縮和殘差壓縮的聯合優化。提出了一種新的多尺度運動融合(MMF)模塊用于點云幀間預測&#xff0c;該模塊提取和融合不同運動流…

首屆UTON區塊鏈開發者計劃大會在馬來西亞圓滿落幕

7月9日&#xff0c;首屆UTON區塊鏈開發者計劃大會在馬來西亞吉隆坡成功舉辦&#xff01; 來自全球頂尖的行業領袖、技術精英和眾多區塊鏈愛好者參與了此次盛會&#xff0c;也標志著UTON區塊鏈生態進入了一個全新的發展階段。 會上&#xff0c;UTON區塊鏈創始人之一唐毅先生以“…

Python 中什么是遞歸函數,如何編寫遞歸函數?

遞歸是計算機科學中的一種基本概念&#xff0c;它指的是函數調用自身的編程技巧。在Python中&#xff0c;遞歸函數是一種通過調用自身來解決問題的函數。這種方法常用于解決可以被分解為較小相同問題的場景&#xff0c;例如階乘計算、斐波那契數列、全排列生成等。 一、遞歸的…

TCP 握手數據流

這張圖詳細描述了 TCP 握手過程中&#xff0c;從客戶端發送 SYN 包到服務器最終建立連接的整個數據流轉過程&#xff0c;包括網卡、內核、進程中的各個環節。下面對每個步驟進行詳細解釋&#xff1a; 客戶端到服務器的初始連接請求 客戶端發送 SYN 包&#xff1a; 客戶端發起…

添加點擊跳轉頁面,優化登錄和注冊頁路由

一、給注銷按鈕添加點擊跳轉至登錄頁 1、在路由中添加登錄頁路由 2、自定義登錄頁面 3、在app.vue頁面找到下拉框組件&#xff0c;添加點擊事件 4、使用vue-router中的useRoute和useRouter 點擊后可以跳轉&#xff0c;但是還存在問題&#xff0c;路徑這里如果我們需要更改登錄…

Linux——公網 IP別名設置,清屏,刪除別名,在linux中提供alias永久化的方法,命令歷史

#### ipe - 公網 IP別名設置&#xff1a; bash alias ipecurl ipinfo.io/ip [rootserver ~]# alias ipecurl ipinfo.io/ip [rootserver ~]# ipe 113.132.176.202[rootserver ~]# #### c - 清屏&#xff0c;一般使用 ctrl l 快捷鍵&#xff0c;也可以將 clear 命令定義得更短&…

JavaScript 作用域 與 var、let、const關鍵字

目錄 一、JavaScript 作用域 1、全局作用域 2、函數作用域 3、塊級作用域 4、綜合示例 5、總結 二、var、let、const 1、var 關鍵字 2、let 關鍵字 3、const 關鍵字 4、總結 5、使用場景 一、JavaScript 作用域 在JavaScript中&#xff0c;作用域是指程序中可訪問…