MyBatis原理剖析(三)--加載配置文件

下面我們正式進入mybatis的源碼學習,之前我們已經了解過mybatis中通過配置文件來保證與數據庫的交互。配置文件分為核心配置文件和映射配置文件,核心配置文件的主要作用就是加載數據庫的一些配置信息而映射配置文件則是執行對應的sql語句。同時核心配置文件中也會集成映射配置文件的路徑信息。

核心配置文件的加載

那么如果mybatis想要執行第一步就需要加載這些配置文件,保證后續與數據庫的操作能夠順利執行。下面我們看看加載的源碼。

//解析配置文件通過類加載器加載為stream流
InputStream resourceAsStream = Resources.getResourceAsStream("/configMapper.xml");//進行加載文件根據文件信息解析出SqlSessionFactory
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);

最外層的方法加載配置文件只用到了兩個方法,分別是通過Resources類的方法得到InputStream?和SqlSessionFactoryBuilder的build來根據InputStream加載出SqlSessionFactoryBuilder。

下面我們進入getResourceAsStream方法來看看這個方法的作用

  /*** Returns a resource on the classpath as a Stream object** @param resource*          The resource to find** @return The resource** @throws java.io.IOException*           If the resource cannot be found or read*/public static InputStream getResourceAsStream(String resource) throws IOException {return getResourceAsStream(null, resource);}/*** Returns a resource on the classpath as a Stream object** @param loader*          The classloader used to fetch the resource* @param resource*          The resource to find** @return The resource** @throws java.io.IOException*           If the resource cannot be found or read*/public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);if (in == null) {throw new IOException("Could not find resource " + resource);}return in;}

在這個方法的內部又調用了重載方法而第一個參數loader則是類加載器,也就是說可以指定類加載器來進行加載這個文件,而在這個getResourceAsStream方法的內部則是調用了一個classLoaderWrapper來進行真正的加載。

  /*** Get a resource from the classpath, starting with a specific class loader** @param resource*          - the resource to find* @param classLoader*          - the first class loader to try** @return the stream or null*/public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {return getResourceAsStream(resource, getClassLoaders(classLoader));}/*** Try to get a resource from a group of classloaders** @param resource*          - the resource to get* @param classLoader*          - the classloaders to examine** @return the resource or null*/InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {for (ClassLoader cl : classLoader) {if (null != cl) {// try to find the resource as passedInputStream returnValue = cl.getResourceAsStream(resource);// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resourceif (null == returnValue) {returnValue = cl.getResourceAsStream("/" + resource);}if (null != returnValue) {return returnValue;}}}return null;}

點開classLoaderWrapper的這個方法可以發現依舊是調用了一個重載方法,并且在調用重載方法之前則是調用了getClassLoaders方法,那么我們打開方法進行查看

  ClassLoader[] getClassLoaders(ClassLoader classLoader) {return new ClassLoader[] { classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(),getClass().getClassLoader(), systemClassLoader };}

可以看到整個方法實際上就是生成了一個類加載器數組,而參數的作用就是在這個數組中再多添加一個類加載器,并且這個數組中的類加載器的順序也是有講究的,優先級高的類加載器則是放在前面,目的則是為了后續策略模式的調用。

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {for (ClassLoader cl : classLoader) {if (null != cl) {// try to find the resource as passedInputStream returnValue = cl.getResourceAsStream(resource);// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resourceif (null == returnValue) {returnValue = cl.getResourceAsStream("/" + resource);}if (null != returnValue) {return returnValue;}}}return null;}

我們繼續回去看接下來的方法可以看到當進入重載方法的時候則是遍歷了作為參數傳遞進來的classLoader數組,然后通過遍歷每個類加載器來選擇正確的類加載器進行加載。這種方式實際上就是設計模式的策略模式,根據不同的參數動態的選擇不同的策略進行執行。

那么這個classLoaderWrapper的作用是什么呢

public class ClassLoaderWrapper {ClassLoader defaultClassLoader;ClassLoader systemClassLoader;ClassLoaderWrapper() {try {systemClassLoader = ClassLoader.getSystemClassLoader();} catch (SecurityException ignored) {// AccessControlException on Google App Engine}}}

點開這個類我們發現這個類內部含有多個類加載器,并且結合其他方法我們發現其實這個類是將多個類加載器進行集成,同時采用了策略模式的方式將不同的類加載器進行加載不同的文件將其轉化為InputStream。也就是說整個方法的核心就是在于這個ClassLoaderWrapper類,它內部集成了多個類加載器,并且根據傳遞的文件路徑通過此類加載器進行加載,最終返回成一個InputStream來表示整個方法的執行完畢。

至此getResourceAsStream方法解析完畢,那么我們從這個方法的源碼中其實可以學習到策略模式的使用以及在同一個類的不同重載方法并不是說是毫不相干的就像mybatis源碼中那樣,基本每個重載類最后都會調用另一個重載類,而最后真正執行業務邏輯的則只有一個重載類。其他類則是更多對于參數的封裝和校驗等操作最后調用真正執行業務邏輯的類。

之后我們再看看下一個方法

//進行加載文件根據文件信息解析出SqlSessionFactory
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);

我們看到這個方法是通過一個SqlSessionFactory的構造器來根據InputStream進行構造返回一個SqlSessionFactory對象,這里有涉及到了兩種設計模式生成器模式和工廠模式

下面我們進入方法內部看看如何解析配置文件的

public class SqlSessionFactoryBuilder {public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (reader != null) {reader.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment) {return build(inputStream, environment, null);}public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);}
}

上述代碼可以看出依舊是一個方法內部調用了重載方法,而真正執行業務邏輯的只有一個重載方法

可以看出最終是調用了這個方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (inputStream != null) {inputStream.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}

而除了inputStream之外則是含有另外兩個參數,這兩個參數的主要作用就是選擇執行配置文件中的哪些配置,因為一個核心配置文件是可以有多個配置的數據源和運行環境的,而另外兩個參數則是決定優先使用哪個數據源和運行環境。

代碼第一行就new了一個XMLConfigBuilder對象,而這個XMLConfigBuilder對象用的很明顯也是生成器模式。下面我們進入這個對象內部來進行看看。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(Configuration.class, inputStream, environment, props);}public XMLConfigBuilder(Class<? extends Configuration> configClass, InputStream inputStream, String environment,Properties props) {this(configClass, new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,Properties props) {super(newConfig(configClass));ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}

進入方法中依舊是調用了多個構造函數來進行參數封裝真正執行的業務邏輯的構造函數只有一個,其中我們看到Configuration這個類,這個類是整個mybatis加載文件的核心類,它的主要作用就是集成配置文件中所解析出來的所有信息于這個類中。并且后續的sql方法執行也是根據這個類內部的屬性進行數據源等其他信息的配置來完成執行。因此這個Configuration類則是整個mybatis加載配置文件中的核心中的核心。而后續的XPathParser類則是將inputStream流直接解析為document文件。XMLConfigBuilder內部會生成一個初始化的Configuration類,并且在后續的parse方法中進行Configuration類對象的賦值。可能會產生一個疑問,直接操作成員變量不會造成線程安全的問他碼,其實這里則是采用了一個非常巧妙的設計。線程隔離機制,我們發現所有直接操作成員變量的對象都是在方法體中直接new出來的對象,也就是說每個線程都有自己的獨有的對象,這樣進行訪問的時候就是單線程訪問了從而達成了線程隔離的效果。

  public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

最終我們發現是通過XMLConfigBuilder來將xml對象進行解析隨后注入到Configuration對象中最后再將Configuration對象放入注入到SqlSessionFactory對象中完成真正的構建sql工廠。

至此配置文件算是完成了解析并且注入。從讀源碼的過程中我們發現源碼用到了多種設計模式:生成器模式--這個模式的主要目的就是講對象生成過程中的復雜邏輯進行封裝,外部使用者直接調用生成器的方法就可以返回成品對象,降低了代碼的耦合度。以及工廠模式等設計模式。通過理解這些源碼的代碼風格以及設計模式將會學到很多知識。

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

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

相關文章

C++(運算符重載)

一.友元 C中使用關鍵字friend可以在類外訪問所有的成員&#xff0c;包括私有成員&#xff08;之前提到過封裝的核心思想是隱藏內部實現細節&#xff0c;通過公共接口控制訪問&#xff09;&#xff0c;所以友元可以突破封裝的限制訪問數據&#xff0c;盲目使用會導致程序穩定性…

XR-RokidAR-UXR3.0-Draggable 腳本解析

using System.Collections.Generic; using Rokid.UXR.Utility; using UnityEngine; using UnityEngine.EventSystems;namespace Rokid.UXR.Interaction {/// <summary>/// Draggable 拖拽組件/// </summary>// [RequireComponent(typeof(RayInteractable))]public …

GitHub 趨勢日報 (2025年06月17日)

&#x1f4ca; 由 TrendForge 系統生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日獲星趨勢圖 今日獲星趨勢圖 1022 anthropic-cookbook 986 awesome-llm-apps 910 fluentui-system-icons 754 r…

NodeJS的中間件是什么

說簡單一點&#xff0c;中間件就是在你的請求和業務邏輯之間做一層攔截。 在 Node.js 中&#xff0c;中間件&#xff08;Middleware&#xff09; 是一種函數&#xff0c;它在 請求&#xff08;Request&#xff09;到達路由處理器之前&#xff0c;或在 響應&#xff08;Respons…

MCAL學習(6)——診斷、DCM

1.診斷概述 汽車診斷就是通過汽車總線&#xff08;CAN LIN Eth&#xff09;來進行診斷會話&#xff0c;大部分通過CAN總線通訊進行請求與響應。 1.診斷分層 DCM內部支持UDS服務和OBD服務&#xff08;排放&#xff0c;動力&#xff09;。 以統一診斷服務UDS為例&#xff0c;應…

kafka-生產者-(day-4)

day-3 BufferPool 產生原因&#xff1a;ByteBuffer的創建和釋放都是比較耗費資源的&#xff0c;為了實現內存的高效利用&#xff0c;產生了他。他會對特定大小的ByteBuffer進行管理 BufferPool的字段 free:是一個ArrayDeque隊列&#xff0c;緩存指定大小的ByteBuffer對象Re…

java 驗證ip是否可達

默認IP的設備已開放ping功能 代碼 public class PingTest {public static void main(String[] args) throws Exception {String ip "192.168.21.101";boolean reachable InetAddress.getByName(ip).isReachable(3000);System.out.println(ip (reachable ? &quo…

LeetCode 2187.完成旅途的最少時間

題目&#xff1a; 給你一個數組 time &#xff0c;其中 time[i] 表示第 i 輛公交車完成 一趟旅途 所需要花費的時間。 每輛公交車可以 連續 完成多趟旅途&#xff0c;也就是說&#xff0c;一輛公交車當前旅途完成后&#xff0c;可以 立馬開始 下一趟旅途。每輛公交車 獨立 運…

永磁同步電機無速度算法--基于正切函數鎖相環的滑模觀測器

最近在學習鎖相環&#xff0c;后續會記錄一下了解到的幾種PLL。 一、原理介紹 傳統鎖相環控制框圖如下所示 在電機正轉時&#xff0c;傳統鎖相環可以實現很好的轉速和轉子位置估計&#xff0c;但是當電機反轉&#xff0c;反電動勢符號發生變化&#xff0c;系統估計轉子位置最…

Vim-vimrc 快捷鍵映射

Vim-vimrc 快捷鍵映射 文章目錄 Vim-vimrc 快捷鍵映射Leader 鍵快捷鍵映射&#xff1a;插入特定字符插入 --插入 ##插入 解釋Leader鍵設置快速插入分隔線 Leader 鍵 我們還將 , 設置為 Leader 鍵&#xff0c;使得其他快捷鍵映射更加簡潔。 let mapleader ","快捷鍵…

SylixOS armv7 任務切換

SylixOS 操作系統下&#xff0c;任務切換可以分為兩種 中斷退出時&#xff0c;執行的任務切換&#xff08;_ScheduleInt&#xff09;內核退出時&#xff0c;執行的任務切換&#xff08;_Schedule&#xff09; 下面分別講講這兩種任務切換 1、中斷退出時任務切換 關于 ARM 架…

Java 自定義異常:如何優雅地處理程序中的“業務病”?

&#x1f525;「炎碼工坊」技術彈藥已裝填&#xff01; 點擊關注 → 解鎖工業級干貨【工具實測|項目避坑|源碼燃燒指南】 一、從一個真實場景開始&#xff1a;銀行轉賬系統的困境 假設你正在開發一個銀行轉賬系統&#xff0c;當用戶嘗試轉賬時可能出現以下問題&#xff1a; 轉…

【JAVA】【Stream流】

1. filter操作 filter()方法用于根據給定的條件過濾列表中的元素&#xff0c;僅保留滿足條件的項。 List<Integer> list Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);List<Integer> res list.stream().filter(a -> a % 2 0).collect(Collectors.toList());for(I…

四、Redis實現限流

簡介&#xff1a; 限流算法在分布式領域是一個經常被提起的話題&#xff0c;當系統的處理能力有限時&#xff0c;如何阻止計劃外的請求繼續對系統施壓。 系統要限定用戶的某個行為在指定的時間里只能允許發生 N 次&#xff0c;如何使用 Redis 的數據結構來實現這個限流的功能&a…

基于Geotools的兩條道路相交并根據交點形成新路線實戰-以OSM數據為例

目錄 前言 一、需求場景及分解 1、需求場景 2、需求應用 二、需求實現 1、加載路網數據 2、獲取道路信息 3、相交點求解 4、生成新道路 5、結果可視化 三、總結 前言 在當今數字化迅速發展的時代&#xff0c;地理空間數據的處理與分析已成為眾多領域不可或缺的關鍵技…

goland有基礎速通(需要其它編程語言基礎)

tip: 無論是變量、方法還是struct的訪問權限控制都是通過命名控制的&#xff0c;命名的首字母是大寫就相當于java中的public&#xff0c;小寫的話就是private&#xff0c;&#xff08;private只有本包可以訪問&#xff09; 1 go的變量聲明 普通變量 特點&#xff1a; 變量類…

量化面試綠皮書:19. 相關系數

文中內容僅限技術學習與代碼實踐參考&#xff0c;市場存在不確定性&#xff0c;技術分析需謹慎驗證&#xff0c;不構成任何投資建議。 19. 相關系數 假設有三個隨機變量x、y和z。 x與y之間的相關系數為0.8&#xff0c;x與z之間的相關系數也是0.8。 Q: 那么y與z之間的最大相關…

新生活的開啟:從 Trae AI 離開后的三個月

很久沒有寫文章了&#xff0c;想借著入職新公司一個月的機會&#xff0c;和大家嘮嘮嗑。 離職 今年2月份我從字節離職了&#xff0c;結束了四年的經歷&#xff0c;當時離開的核心原因是覺得加班時間太長了&#xff0c;平均每天都要工作15&#xff0c;16個小時&#xff0c;周末…

LLM部署之vllm vs deepspeed

部署大語言模型(如 Qwen/LLaMA 等)時,vLLM 與 DeepSpeed 是當前主流的兩種高性能推理引擎。它們各自專注于不同方向,部署流程也有明顯區別。 vLLM 提供極致吞吐、低延遲的推理服務,適用于在線部署;DeepSpeed 更側重訓練與推理混合優化,支持模型并行,適用于推理 + 微調/…

Git(二):基本操作

文章目錄 Git(二)&#xff1a;基本操作添加文件修改文件版本回退撤銷修改情況一&#xff1a;工作區的代碼還沒有 add情況?&#xff1a;已經 add 但沒有 commit情況三&#xff1a;已經 add 并且也 commit 刪除文件 Git(二)&#xff1a;基本操作 添加文件 首先我們先來學習一個…