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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;基本操作 添加文件 首先我们先来学习一个…