Spring源碼_05_IOC容器啟動細節

前面幾章,大致講了SpringIOC容器的大致過程和原理,以及重要的容器和beanFactory的繼承關系,為后續這些細節挖掘提供一點理解基礎。掌握總體脈絡是必要的,接下來的每一章都是從總體脈絡中,

去研究之前沒看的一些重要細節。

本章就是主要從Spring容器的啟動開始,查看一下Spring容器是怎么啟動的,調用了父類的構造方法有沒有干了什么。😄

直接從創建容器為切入點進去:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean(User.class);

進去之后會調用到這個方法:

可以看到這里是分了三步:

1、調用父類構造方法

2、設置配置文件地址

3、刷新容器

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {//調用父類構造方法,其實沒做啥,就是如果有父容器(默認啥空),設置父容器和合并父容器的environment到當前容器super(parent);//設置配置文件地址:如果有用了$、#{}表達式,會解析到這些占位符,拿environment里面到屬性去替換返回setConfigLocations(configLocations);if (refresh) {//刷新容器,是Spring解析配置,加載Bean的入口。// 用了模板方法設計模型:規定了容器中的一系列步驟refresh();}
}

1. super(parent)-調用父類構造方法

其實這個方法點進去,會調用到一系列父類的super方法,但是最終只是調用到了 AbstractApplicationContext的構造方法(其實每個父類里面對應的屬性都可以看一看,有些都是直接初始化默認的)

/*** Create a new AbstractApplicationContext with the given parent context.* @param parent the parent context*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {//會初始化resourcePatternResolver屬性為PathMatchingResourcePatternResolver//就是路徑資源解析器,比如寫的"classpath:*",會默認去加載classpath下的資源this();//設置父容器。并會copy父容器的environment屬性合并到當前容器中setParent(parent);
}

1.1 this()

接下來調用自己的this方法

public AbstractApplicationContext() {//設置資源解析器this.resourcePatternResolver = getResourcePatternResolver();
}

就是設置了自己的resourcePatternResolver資源解析器

1.1.1 getResourcePatternResolver()

這個代碼沒啥,就是創建了一個默認的資源解析處理器 PathMatchingResourcePatternResolver

protected ResourcePatternResolver getResourcePatternResolver() {return new PathMatchingResourcePatternResolver(this);
}

其實這個對象的功能就是把你傳進來的字符串的路徑,解析加載到具體的文件,返回Spring能識別的Resource對象

ok,this方法走完了應該就繼續走之前的setParent(parent)方法

1.2 setParent(parent)

其實這里目前就是走不進去的,默認的parent父容器我們這里沒使用,所以是空的,并不會走if的邏輯

但是代碼也挺簡單,其實就是設置了parent屬性,合并父容器的Environment到當前容器的Environment

public void setParent(@Nullable ApplicationContext parent) {this.parent = parent;//如歌有父容器,則合并父容器的Environment的元素到當前容器中//合并PropertySource(也就是key和value)//合并激活activeProfiles文件列表//合并默認文件列表defaultProfilesif (parent != null) {Environment parentEnvironment = parent.getEnvironment();if (parentEnvironment instanceof ConfigurableEnvironment) {getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);}}
}

當然,可以假設我們設置了parent屬性。

會先調用到getEnvironment方法,獲取環境對象,如果沒有的話,會創建一個默認的

1.2.1 getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {if (this.environment == null) {this.environment = createEnvironment();}return this.environment;
}

默認是空的,會跑到createEnvironment方法

1.2.1.1 createEnvironment()
protected ConfigurableEnvironment createEnvironment() {return new StandardEnvironment();
}

會初始化一個StandardEnvironment類型的對象,我們可以關注他的構造方法,其實并沒有內容,但是會默認調用他的父類AbstractEnvironment構造器的方法

public AbstractEnvironment() {//這里會默認加載屬性屬性變量和環境信息this(new MutablePropertySources());
}

1.2.1.1.1 new MutablePropertySources()

其實這個對象就是使用了迭代器的設計模式,里面用 propertySourceList數組存儲不同類型的PropertySource

那么PropertySource是干嘛的呢??

//存放Environment對象里的每個屬性,一個PropertySource對象里面存有不同的Properties對象
//Properties對象就是有key和value的鍵值對象
//比如name=systemProperties -> 系統屬性Properties對象
//比如name=systemEnv -> 系統環境變量Properties對象
public abstract class PropertySource<T> {protected final Log logger = LogFactory.getLog(getClass());protected final String name;protected final T source;
}

這里摘取了他的屬性。

其實name只是一個類型而已,比如Environment包括了systemProperties(系統屬性)和systemEnv(系統環境變量)兩種。對應就是不同的name的屬性存儲器

source屬性一般都是Java中的Properties對象,這個對象大家應該都熟悉吧(就跟map差不多,有keyvalue,一般用于讀取properties文件使用)

看一下下面的圖就知道了,Environment在Spring中算是非常重要的對象了,所以必須了解

好了,知道了創建了這個默認的對象即可。

接下來就是調用AbstractEnvironmentthis方法進去了。

AbstractEnvironment(MutablePropertySources)
protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources = propertySources;//創建屬性解析器PropertySourcesPropertyResolverthis.propertyResolver = createPropertyResolver(propertySources);//調用子類的方法,加載系統的環境變量和系統屬性到environment中customizePropertySources(propertySources);
}

可以看到這里就是設置了Environment內部的propertySources對象(存儲屬性的容器),

設置了propertyResolver屬性解析器,類型為PropertySourcesPropertyResolver還把剛剛那個propertySources設置進去了,這個解析器在后面會用到(在設置配置文件路徑時會解析,后面會聊到!)

接下來非常重要的方法就是customizePropertySources方法了,其實在當前類AbstractEnvironment中是空方法,是子類 StandardEnvironment實現的。(這里是不是很熟悉的味道,又是模版方法設計模式,AbstractEnvironment規定了步驟,調用了當前類的空方法,子類會去覆蓋這個空方法)😄

ok,我們進來了子類StandardEnvironmentcustomizePropertySources方法

其實可以看到這里就是寫了兩句代碼,分別就是去讀取系統屬性和系統環境變量的值,加載到Environment

public class StandardEnvironment extends AbstractEnvironment {/** System environment property source name: {@value}. */public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";/** JVM system properties property source name: {@value}. */public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {//添加系統屬性和系統環境變量,封裝了一個個propertySource對象,添加到Environment的propertySources屬性列表中propertySources.addLast(//系統屬性new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(//系統環境變量new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}}

我們可以看其中一個方法getSystemEnvironment,就是調用了jdk的System.getenv()方法,去獲取到你本機的系統環境變量的值,然后最后設置到propertySources -> Environment

@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemEnvironment() {if (suppressGetenvAccess()) {return Collections.emptyMap();}try {//jdk提供的方法,獲取系統的環境變量return (Map) System.getenv();}catch (AccessControlException ex) {return (Map) new ReadOnlySystemAttributesMap() {@Override@Nullableprotected String getSystemAttribute(String attributeName) {try {return System.getenv(attributeName);}catch (AccessControlException ex) {if (logger.isInfoEnabled()) {logger.info("Caught AccessControlException when accessing system environment variable '" +attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());}return null;}}};}
}

解析完的Environment的里面的值大概是這樣:

到這里,應該是理解Environment對象了吧。😄

okk,?🏻回到之前的調用getEnvironment的地方,咱們已經看完這個方法啦!也就是標題1.2

接下里有了Environment對象,就會進行父子容器的Environment的合并啦!

1.2.2 Environment.merge()-父子容器的Environment合并

這里的代碼就非常簡單了,主要就是合并父容器的Environment的屬性到當前子容器中

public void merge(ConfigurableEnvironment parent) {
//合并PropertySource,也就是具體存在的屬性鍵值對
for (PropertySource<?> ps : parent.getPropertySources()) {if (!this.propertySources.contains(ps.getName())) {this.propertySources.addLast(ps);}
}
//合并活躍的profile - 一般SpringBoot中多開發環境都會設置profile
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {synchronized (this.activeProfiles) {Collections.addAll(this.activeProfiles, parentActiveProfiles);}
}
//合并默認的profile
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {synchronized (this.defaultProfiles) {this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);Collections.addAll(this.defaultProfiles, parentDefaultProfiles);}
}
}

ok,到這里標題1,調用父類構造的方法到這里就結束了,接下來繼續探索setConfigLocations干了什么。

2. setConfigLocations-設置配置文件路徑

/*** 設置配置文件地址,并且會將文件路徑格式化成標準格式* 比如applicationContext-${profile}.xml, profile存在在Environment。* 假設我的Environment中有 profile = "dev",* 那么applicationContext-${profile}.xml會被替換成 applicationContext-dev.xml* Set the config locations for this application context.* <p>If not set, the implementation may use a default as appropriate.*/
public void setConfigLocations(@Nullable String... locations) {if (locations != null) {//斷言,判讀當前配置文件地址是空就跑出異常Assert.noNullElements(locations, "Config locations must not be null");this.configLocations = new String[locations.length];for (int i = 0; i < locations.length; i++) {//解析當前配置文件的地址,并且將地址格式化成標準格式this.configLocations[i] = resolvePath(locations[i]).trim();}}else {this.configLocations = null;}
}

這里關鍵的方法是會調用到resolvePath方法并返回這些字符串路徑

點進去,有沒有感覺到很驚喜,為什么用了getEnvironment去調用的呢?

其實之前的getEnvironment并沒有執行到,因為我們沒有設置父類parent,到這里才是第一次初始化這個Environment對象然后調用它的resolveRequiredPlaceholders方法去解析路徑

(這里關Environment什么事呢?其實我們可以動態地寫我們的配置文件,配置文件會去讀取占位符,判斷在Environment是否存在這些屬性,并完成替換)

protected String resolvePath(String path) {//這里的獲取getEnvironment,會默認創建StandardEnvironment對象。//并用這個Environment對象解析路徑return getEnvironment().resolveRequiredPlaceholders(path);
}

寫個示例就清楚咯!

2.1. 示例

我的電腦中存在HOME這個環境變量

接下來修改我的配置文件名稱:

修改完之后發現,配置文件路徑確定給解析到了。

了解這個功能即可。平時很少這么使用

ok,解析完配置,接下來就是最核心的方法了,調用refresh容器刷新方法

3. refresh-容器刷新方法

這個方法是IOC的核心方法,只要掌握這個方法中的每一個方法,其實就基本掌握了Spring的IOC的整個流程。

后面將會分為很多章節去解釋每個方法。

/*** 容器刷新方法,是Spring最核心到方法。* 規定了容器刷新到流程:比如prepareRefresh 前置刷新準備、* obtainFreshBeanFactory 創建beanfactory去解析配置文、加載beandefinition、* prepareBeanFactory 預設置beanfactory、* invokeBeanFactoryPostProcessors 執行beanfactoryPostProcessor* registerBeanPostProcessors 注冊各種beanPostProcesser后置處理器* initMessageSource 國際化調用* initApplicationEventMulticaster 初始化事件多播器* onRefresh 刷新方法,給其他子容器調用,目前這個容器沒干啥* registerListeners 注冊時間監聽器* finishBeanFactoryInitialization 初始化所有非懶加載的bean對象到容器中* finishRefresh 容器完成刷新: 主要會發布一些事件** @throws BeansException* @throws IllegalStateException*/
@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// Prepare this context for refreshing.//容器刷新的前置準備//設置啟動時間,激活狀態為true,關閉狀態false//初始化environment//初始化監聽器列表prepareRefresh();// Tell the subclass to refresh the internal bean factory.//創建beanFactory對象,并且掃描配置文件,加載beanDeifination,注冊到容器中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.//BeanFactory的預準備處理,設置beanFactory的屬性,比如添加各種beanPostProcessor//設置environment為bean對象并添加到容器中,后面可以直接@autowrie注入這些對象prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//子類去實現的回調方法,當前容器沒做什么工作,是個空方法postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.//加載并處理beanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//注冊BeanPostProcessor對象到容器中registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.//初始化消息源,國際化使用initMessageSource();// Initialize event multicaster for this context.//初始化事件多播器對象,并注冊到容器中initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//刷新,又是spring為了擴展,做的一個空實現,讓子類可以覆蓋這個方法做增強功能onRefresh();// Check for listener beans and register them.//注冊監聽器到容器中,如果容器中的earlyApplicationEvents列表中有事件列表//就會先發送這些事件。比如可以在前面的onRefresh方法中設置registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//最最重要的方法,根據之前加載好的beandefinition,實例化bean到容器中,//涉及到三級緩存、bean的生命周期、屬性賦值等等finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//完成刷新,會發送事件。//檢查earlyApplicationEvents事件列表中有沒有新增的未發送的事件,有就發送// 在執行applicationEventMulticaster事件列表中的所有事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}
}

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

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

相關文章

WPF使用OpenCvSharp4

WPF使用OpenCvSharp4 創建項目安裝OpenCvSharp4 創建項目 安裝OpenCvSharp4 在解決方案資源管理器中&#xff0c;右鍵單擊項目名稱&#xff0c;選擇“管理 NuGet 包”。搜索并安裝以下包&#xff1a; OpenCvSharp4OpenCvSharp4.ExtensionsOpenCvSharp4.runtime.winSystem.Man…

leetcode 3083. 字符串及其反轉中是否存在同一子字符串 簡單

給你一個字符串 s &#xff0c;請你判斷字符串 s 是否存在一個長度為 2 的子字符串&#xff0c;在其反轉后的字符串中也出現。 如果存在這樣的子字符串&#xff0c;返回 true&#xff1b;如果不存在&#xff0c;返回 false 。 示例 1&#xff1a; 輸入&#xff1a;s "…

TCP-UDP調試工具推薦:Socket通信測試教程(附詳細圖解)

前言 在網絡編程與應用開發中&#xff0c;調試始終是一項不可忽視的重要環節。尤其是在涉及TCP/IP、UDP等底層網絡通信協議時&#xff0c;如何確保數據能夠準確無誤地在不同節點間傳輸&#xff0c;是許多開發者關注的核心問題。 調試的難點不僅在于定位連接建立、數據流控制及…

Vue.js框架:在線教育系統的安全性與穩定性

2.1系統開發使用的關鍵技術 本系統在開發中選擇B/S框架進行設計&#xff0c;語言采用Java&#xff0c;數據庫采用Mysql&#xff0c;并在設計中加入VUE.js技術&#xff0c;本系統的運行環境為Idea。 2.2 VUE.js技術介紹 VUE.js是一個用來開發前臺界面的JavaScript框架&#xff0…

【新方法】通過清華鏡像源加速 PyTorch GPU 2.5安裝及 CUDA 版本選擇指南

下面詳細介紹所提到的兩條命令&#xff0c;它們的作用及如何在你的 Python 環境中加速 PyTorch 等庫的安裝。 1. 設置清華鏡像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple這條命令的作用是將 pip &#xff08;Python 的包管理工具&#xf…

【數據結構】單鏈表的使用

單鏈表的使用 1、基本概念2、鏈表的分類3、鏈表的基本操作a、單鏈表節點設計b、單鏈表初始化c、單鏈表增刪節點**節點頭插&#xff1a;****節點尾插&#xff1a;****新節點插入指定節點后&#xff1a;**節點刪除&#xff1a; d、單鏈表修改節點e、單鏈表遍歷&#xff0c;并打印…

虛幻引擎是什么?

Unreal Engine&#xff0c;是一款由Epic Games開發的游戲引擎。該引擎主要是為了開發第一人稱射擊游戲而設計&#xff0c;但現在已經被成功地應用于開發模擬游戲、恐怖游戲、角色扮演游戲等多種不同類型的游戲。虛幻引擎除了被用于開發游戲&#xff0c;現在也用于電影的虛擬制片…

Linux(Centos 7.6)yum源配置

yum是rpm包的管理工具&#xff0c;可以自動安裝、升級、刪除軟件包的功能&#xff0c;可以自動解決軟件包之間的依賴關系&#xff0c;使得用戶更方便軟件包的管理。要使用yum必須要進行配置&#xff0c;個人將其分為三類&#xff0c;本地yum源、局域網yum源、第三方yum源&#…

Linux上更新jar包里的某個class文件

目標&#xff1a;替換voice-1.0.jar里的TrackHandler.class文件 一.查詢jar包里TrackHandler.class所在的路徑 jar -tvf voice-1.0.jar |grep TrackHandler 二.解壓出TrackHandler.class文件 jar -xvf voice-1.0.jar BOOT-INF/classes/com/yf/rj/handler/TrackHandler.cla…

機器學習中回歸預測模型中常用四個評價指標MBE、MAE、RMSE、R2解釋

在機器學習中&#xff0c;評估模型性能時常用的四個指標包括平均絕對誤差&#xff08;Mean Absolute Error, MAE&#xff09;、均方誤差&#xff08;Mean Squared Error, MSE&#xff09;、均方根誤差&#xff08;Root Mean Squared Error, RMSE&#xff09;和決定系數&#xf…

基于SpringBoot的Jwt認證以及密碼aes加密解密技術

目錄 前言 1.SpringBoot項目的創建 2.相關技術 3.項目架構 4.項目關鍵代碼 5.項目最終的運行效果 ?編輯 6.PostMan測試接口結果 前言 學習了SpringBoot之后&#xff0c;才覺得SpringBoot真的很方便&#xff0c;相比傳統的SSH&#xff0c;SSM&#xff0c;SpringBo…

uniapp下載打開實現方案,支持安卓ios和h5,下載文件到指定目錄,安卓文件管理內可查看到

uniapp下載&打開實現方案&#xff0c;支持安卓ios和h5 Android&#xff1a; 1、申請本地存儲讀寫權限 2、創建文件夾&#xff08;文件夾不存在即創建&#xff09; 3、下載文件 ios&#xff1a; 1、下載文件 2、保存到本地&#xff0c;需要打開文件點擊儲存 使用方法&…

77、將adaface的mtcnn模型npy文件轉成atlas310p模型,并進行推理

基本思想:將adaface的mtcnn模型npy文件轉成atlas310p模型進行推理。同時比對結果 ubuntu@ubuntu:~$ git clone https://github.com/mk-minchul/AdaFace.git Cloning into AdaFace... remote: Enumerating objects: 236, done. remote: Counting objects: 100% (109/109), don…

Spark SQL DML語句

【圖書介紹】《Spark SQL大數據分析快速上手》-CSDN博客 《Spark SQL大數據分析快速上手》【摘要 書評 試讀】- 京東圖書 Spark本地模式安裝_spark3.2.2本地模式安裝-CSDN博客 DML&#xff08;Data Manipulation Language&#xff0c;數據操作語言&#xff09;操作主要用來對…

農歷節日倒計時:基于Python的公歷與農歷日期轉換及節日查詢小程序

農歷節日倒計時&#xff1a;基于Python的公歷與農歷日期轉換及節日查詢小程序 摘要 又是一年春節即將到來&#xff0c;突然想基于Python編寫一個農歷節日的倒計時小程序。該程序能夠根據用戶輸入的農歷節日名稱&#xff0c;計算出距離該節日還有多少天。通過使用lunardate庫進…

線性直流電流

電阻網絡的等效 等效是指被化簡的電阻網絡與等效電阻具有相同的 u-i 關系 (即端口方程)&#xff0c;從而用等效電阻代替電阻網絡之后&#xff0c;不 改變其余部分的電壓和電流。 串聯等效&#xff1a; 并聯等效&#xff1a; 星角變換 若這兩個三端網絡是等效的&#xff0c;從任…

CDN(Content Delivery Network,內容分發網絡)

CDN&#xff08;Content Delivery Network&#xff0c;內容分發網絡&#xff09;是一種通過在網絡中部署分布式的服務器集群&#xff0c;將網站內容分發到最接近用戶的服務器節點&#xff0c;以提高用戶訪問速度和穩定性的重要網絡基礎設施。CDN的核心思想是讓用戶就近獲取所需…

B站推薦模型數據流的一致性架構

01 背景 推薦系統的模型&#xff0c;通過學習用戶歷史行為來達到個性化精準推薦的目的&#xff0c;因此模型訓練依賴的樣本數據&#xff0c;需要包括用戶特征、服務端推薦的視頻特征&#xff0c;以及用戶在推薦視頻上是否有一系列的消費行為。 推薦模型數據流&#xff0c;即為…

【LeetCode】839、相似字符串組

【LeetCode】839、相似字符串組 文章目錄 一、并查集1.1 并查集 二、多語言解法 一、并查集 1.1 并查集 求共有幾組, 聯想到并查集, 即并查集有幾個集合 字符串相似: 相差0個字符, 或2個字符 其中所有字符串長度都相同, 是比較方便處理的 // go var sets int var father […

你不需要對其他成年人的情緒負責

在這個紛繁復雜的世界里&#xff0c;每個人都是獨一無二的個體&#xff0c;背負著各自的故事、夢想與煩惱。在人際交往的廣闊舞臺上&#xff0c;我們時常會遇到這樣的情境&#xff1a;朋友、同事、家人&#xff0c;甚至是陌生人&#xff0c;他們的情緒似乎總能不經意間影響到我…