『手撕 Mybatis 源码』02 - 加载配置文件

news2024/12/23 15:49:57

加载配置文件

获取输入流

  1. myBatis 的配置文档层次架构
    在这里插入图片描述
  2. 首先从读入开始查看是怎么加载配置文件的,现在从这里打个断点
public class MybatisTest {
  @Test
  public void test1() throws IOException {
        // 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中
        // 注意:配置文件并没有被解析
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    ...
  }
}
  1. 然后发现会通过一个 classLoaderWrapper: ClassLoader 的包装器来把数据源读入,返回输入流
public class Resources {
  ...
  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
	InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    ...
  }
}
  1. 继续进入会发现其实会通过 getClassLoaders(classLoader) 返回对应加载器之后再进行读取。现在的 classLoader 是 null
public class ClassLoaderWrapper {
  ...
  public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return getResourceAsStream(resource, getClassLoaders(classLoader));
  }
}
  1. 其中 getClassLoaders(classLoader) 代码如下
public class ClassLoaderWrapper {
  // 默认类加载器
  ClassLoader defaultClassLoader;
  // 系统类加载器
  ClassLoader systemClassLoader;
  ...
  ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        // 参数指定的类加载器
        classLoader,
        // 系统指定的默认加载器
        defaultClassLoader,
        // 当前线程绑定的类加载器
        Thread.currentThread().getContextClassLoader(),
        // 当前类使用的类加载器
        getClass().getClassLoader(),
        systemClassLoader};
  }
}
  1. 拿到加载器集合之后才正在的调用读取源方法,最后返回输入流,判断方法如下
public class ClassLoaderWrapper {
  ...
  InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    // 循环 ClassLoader,通过指定或默认的 ClassLoader 读取文件
    for (ClassLoader cl : classLoader) {
      // 如果加载器不是空的
      if (null != cl) {
        // 先用这个加载器尝试读一下
        InputStream returnValue = cl.getResourceAsStream(resource);

        // 如果返回没结果,有可能是因为缺少个 "/",添加后再读一下
        if (null == returnValue) {
          returnValue = cl.getResourceAsStream("/" + resource);
        }
        
        if (null != returnValue) {
          // 如果读取到,终止循环,返回结果
          return returnValue;
        }
      }
    }
    return null;
  }
}
  1. 当输入流读成功之后,就返回
public class Resources {
  ...
  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;
  }
}
  1. 问题:为什么需要 ClassLoaderWrapper 包装器,而不直接使用 ClassLoader?
  • 因为它需要拿来判断资源路径是否为空
  • 根据路径加载好的输入流是否为空
  • 使用的类加载器是否为空
  • 异常处理

解析配置文件

  1. 首先先通过 SqlSessionFactoryBuilder 构建 sqlSessionFactory,我们能知道 sqlSessionFactory 肯定包含了所有配置文件信息
public class MybatisTest {

  @Test
  public void test1() throws IOException {
    ...
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    ...
  }
}
  1. 首先是调用 build() 方法,发现我们 environment 输入的是 null
public class SqlSessionFactoryBuilder {
	...
	  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  // environment 运行环境信息的 id 值
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }
  ...
}
  • 其实正常来说,我们可以自己配置 environment 参数,因为我们知道 Mybatis 是支持多环境配置,定义就在 sqlMapConfig.xml
<configuration>
  ...
    <environment id="development">
	...
    </environment>
  ...
</configuration>
  1. 然后就需要通过 XMLConfigBuilder 来将输入流创建成 document 对象
public class SqlSessionFactoryBuilder {
  ...
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // XMLConfigBuilder:用来解析XML配置文件
      // 使用构建者模式:
      // 	- 好处:降低耦合、分离复杂对象的创建
      // 1. 创建 XPathParser 解析器对象,根据 inputStream 解析成了 document 对象 
      // 2. 创建全局配置对象 Configuration 对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      ...
	}
}

注意:对于返回至少 4 个参数以上的对象,最好使用 构建者模式

  1. XMLConfigBuilder 的构造器中就会通过创建一个 XPathParser 对象,里面就会包含一个 document 对象,也就是 XPathParser 其实只会专门负责解析配置文件而已
public class XMLConfigBuilder extends BaseBuilder {
  ...
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    // XPathParser 基于 Java XPath 解析器,用于解析  MyBatis 中的配置文件
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
}
  • createDocument() 的东西过于细节,就先不关注了,反正最终就是,通过 dom 解析,获取 Document 对象,然后赋值到成员变量中
public class XPathParser {
  ...
  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    // 解析 XML 文档为 Document 对象
    this.document = createDocument(new InputSource(inputStream));
  }
}
  1. 然后开始初始化 XMLConfigBuilder,首先会创建一个 Configuration 对象
public class XMLConfigBuilder extends BaseBuilder {
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //  创建全局 Configuration 对象,并通过 TypeAliasRegistry 注册一些 Mybatis 内部相关类的别名
    super(new Configuration());
    ...
  }
}
  1. 简单来看看 Configuration 的构造方法,可以看到注册了一堆别名器
public class Configuration {
  ...
      // 为什么使用 JDBC 别名就能使用 jdbc 事务管理呢? sqlConfig.xml,原因就是在这
  public Configuration() {
   
    // TypeAliasRegistry(类型别名注册器)
    // 注册事务工厂的别名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    ...
  }
}
  • 问题:为什么使用 JDBC 别名就能使用 jdbc 事务管理呢?
    • 原因就是这里, 因为我们在 sqlMapConfig.xml 中,指定了用哪个工厂来赋值,其中的 type 字段就是
<configuration>
	...
    <environment id="development">
      <!-- 使用jdbc事务管理 -->
      <transactionManager type="JDBC" />
      <!-- 数据库连接池 -->
      <dataSource type="POOLED">
		...
      </dataSource>
    </environment>
	...
</configuration>
  1. 回到 build() 得到 XMLConfigBuilder 后,才真正开始执行分析
public class SqlSessionFactoryBuilder {
  ...
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
   
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

      // parser.parse():使用 XPATH 解析 XML 配置文件,将配置文件封装到 Configuration 对象
      // 返回 DefaultSqlSessionFactory 对象,该对象拥有 Configuration 对象(封装配置文件信息)
      // parse():配置文件就解析完成了
      return build(parser.parse());
  }
}

XMLConfigBuilder 解析 Document 获取配置存入 configuration

  1. 继续从 parser.parse() 里面进入会看到 parse() 只会解析一次
public class XMLConfigBuilder extends BaseBuilder {
  ...
  public Configuration parse() {
    // 一开始是 false,只会计息一次
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;

    // parser.evalNode("/configuration"):通过 XPATH 解析器,解析 configuration 根节点
    // 从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中
    parseConfiguration(parser.evalNode("/configuration"));

    return configuration;
  }
}
  1. 然后通过 evalNode() 将解析 /configuration 下的所有内容成 Xnode
public class XPathParser {
  ...
  public XNode evalNode(String expression) {
    // 根据XPATH语法,获取指定节点
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) { // 获取需选中的内容,转换成 xnode
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }
  ...
}
  • 其中 Xnode 包含的内容就是 sqlMapConfig.xml 标签 <configuration> 下的所有内容
  1. 进入到 parseConfiguration() 之后会发现很多 **Element() 的函数,很关键,这些都是把 /configuration 节点下的内容解析出来,存到 configuration 对象中
public class XMLConfigBuilder extends BaseBuilder {
	...
	  private void parseConfiguration(XNode root) {
    try {
      // 解析 </properties> 标签
      propertiesElement(root.evalNode("properties"));
      // 解析 </settings> 标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 解析 </typeAliases> 标签
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析 </plugins> 标签
      pluginElement(root.evalNode("plugins"));
      // 解析 </objectFactory> 标签
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析 </objectWrapperFactory> 标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析 </reflectorFactory> 标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // 解析 </environments> 标签
      environmentsElement(root.evalNode("environments"));
      // 解析 </databaseIdProvider> 标签
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析 </typeHandlers> 标签
      typeHandlerElement(root.evalNode("typeHandlers"))
      // 解析 </mappers> 标签 加载映射文件流程主入口
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
}
  1. 跳到 environmentsElement() 看看
  • 首先它会从 Xnode 节点中提取 /environments 元素,然后判断存不存在内容
  • 然后判断是否传入过 environment 参数,如果没有,就从 /environments 拿到 default 属性 要激活的环境
  • 拿到环境后,开始根据每个标签下定义的 type 属性 创建各个工厂类,最后封装到 configuration
public class XMLConfigBuilder extends BaseBuilder {
  ...
  private void environmentsElement(XNode context) throws Exception {
    // 如果定义了environments
    if (context != null) {
      // 方法参数如何没有传递 environment,则解析 sqlMapConfig.xml 中的
      if (environment == null) {
        // <environments default="development" >
        environment = context.getStringAttribute("default");
      }

      //遍历解析 environment 节点,找到生效的 environment
      for (XNode child : context.getChildren()) {
        // 获得 id 属性值
        String id = child.getStringAttribute("id");
        // 判断 id 和 environment 值是否相等
        if (isSpecifiedEnvironment(id)) {

          // <transactionManager type="JDBC" /> 创建事务工厂对象
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));

          // <dataSource type="POOLED"> 创建数据源对象
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);

          // 将 Environment 存到 configuraion 中
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }
}
  • transactionManagerElement()type 属性 作用就是起的别名里面已经有对应工厂类了,直接初始化即可
public class XMLConfigBuilder extends BaseBuilder {
  ...
  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }
}
  1. 再来看看 mapperElement() 解析 /mappers 的情况
  • 它会根据子标签 /package 还是 /mapper 进行处理,先简单看看 /mapper 的解析
  • 它会从 /mapper 中解析出 resource 属性url 属性class 属性,这 3 者是互斥的,只能用其中一个
  • resource 属性 的解析,还需要使用 XMLMapperBuilder 来专门解析
public class XMLConfigBuilder extends BaseBuilder {
  ...
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 获取 <mappers> 标签的子标签
      for (XNode child : parent.getChildren()) {
        // <package> 子标签
        if ("package".equals(child.getName())) {
			...
        } else {// <mapper>子标签
          // 获取<mapper>子标签的 resource 属性
          String resource = child.getStringAttribute("resource");
          // 获取 <mapper> 子标签的 url 属性
          String url = child.getStringAttribute("url");
          // 获取 <mapper> 子标签的 class 属性
          String mapperClass = child.getStringAttribute("class");
          // 它们是互斥的
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              // 专门用来解析 mapper 映射文件
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              // 通过 XMLMapperBuilder 解析 mapper 映射文件
              mapperParser.parse();
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              // 通过 XMLMapperBuilder 解析 mapper 映射文件
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 将指定 mapper 接口以及它的代理对象存储到一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
  ...
}
  1. 总结
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/531565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MidJourney中国版开放内测;70款ChatGPT插件全评测;盘点181家海外AI创业公司;GPT+科研工作流 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『左耳朵耗子 | 享受编程和技术带来的快乐』Coding Your Ambition CoolShell 是陈皓创建的技术共享平台&#xff0c;主题非常广泛&…

加密解密软件VMProtect教程(五):主窗口之主菜单

VMProtect 是保护应用程序代码免遭分析和破解的可靠工具&#xff0c;但只有在正确构建应用程序内保护机制并且没有可能破坏整个保护的典型错误的情况下才能最有效地使用。 接下来为大家介绍关于VMProtect中主菜单的功能介绍&#xff0c;包括文件、编辑、项目、工具和帮助。 &…

基于SSM+JSP的人体健康信息管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

PV270R1K1T1WMMC_PARKER轴向柱塞泵

PV270R1K1T1WMMC_PARKER轴向柱塞泵 柱塞泵分类 PARKER柱塞泵根据倾斜元件的不同&#xff0c;有斜盘式和斜轴式两种。斜盘式是斜盘相对回转的缸体有一倾斜角度&#xff0c;而引起柱塞在泵缸中往复运动。传动轴轴线和缸体轴线是一致的。这种结构较简单&#xff0c;转速较高&…

Git教程(二)

工作区和暂存区 工作区&#xff08;Working Directory&#xff09; learngit 文件夹就是一个工作区。 版本库&#xff08;Repository&#xff09; 工作区有个隐藏目录 .git &#xff0c;这个不算工作区&#xff0c;而是 Git 的版本库。 版本库里面的 index(stage) 文件叫暂…

掌握无人机遥感数据预处理的全链条理论与实践流程、典型农林植被性状的估算理论与实践方法、利用MATLAB进行编程实践(脚本与GUI开发)以及期刊论文插图制作

在新一轮互联网信息技术大发展的现今&#xff0c;无人机、大数据、人工智能、物联网等新兴技术在各行各业都处于大爆发的前夜。为了将人工智能方法引入农业生产领域。首先在种植、养护等生产作业环节&#xff0c;逐步摆脱人力依赖&#xff1b;在施肥灌溉环节构建智慧节能系统&a…

尚硅谷大数据技术-教程-学习路线-笔记汇总表【课程资料下载】

&#x1f618; 目录 00【前言】 01【大数据学习路线&#xff08;快速版&#xff09;】 02【视频地址&资料下载】 03【课程笔记】 001-Linux 002-Hadoop 003-Zookeeper 004【Scala】 005【Spark】 006【Nifi】 007【kafka】 008【flink】 00【前言】 都是公开的…

“深入探索SDL游戏开发“

前言 欢迎来到小K的SDL专栏第二小节&#xff0c;本节将为大家带来基本窗口构成、渲染器、基本图形绘制、贴图、事件处理等的详细讲解&#xff0c;看完后希望对你有收获 文章目录 前言一、简单窗口二、渲染器三、基本图形绘制1、点2、线3、矩形4、圆和椭圆 四、贴图五、事件处理…

Java经典笔试题—day09

Java经典笔试题—day09 &#x1f50e;选择题&#x1f50e;编程题&#x1f95d; 另类加法&#x1f95d;走方格的方案数 &#x1f50e;结尾 &#x1f50e;选择题 (1)下面程序的输出是 ( ) String x“fmn”; x.toUpperCase(); String yx.replace(‘f’,‘F’); yy“wxy”; Syste…

数据结构lab3-图型结构的建立与搜索

title: 数据结构lab3-图型结构的建立与搜索 date: 2023-05-16 11:42:26 tags: 数据结构与算法 课程名称&#xff1a;数据结构与算法 课程类型&#xff1a;必修 实验项目&#xff1a;图型结构的建立与搜索 实验题目&#xff1a;图的存储结构的建立与搜索 实验日期&#xff1…

基于html+css的图展示72

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

【K8s】openEuler23操作系统安装Docker和Kubernetes

openEuler23操作系统安装 服务器搭建环境随手记 文章目录 openEuler23操作系统安装前言&#xff1a;一、前期准备&#xff08;所有节点&#xff09;1.1所有节点&#xff0c;关闭防火墙规则&#xff0c;关闭selinux&#xff0c;关闭swap交换&#xff0c;打通所有服务器网络&am…

Java 工程师不同阶段的发展路线

Java作为一种广泛应用于企业级应用程序的编程语言&#xff0c;已成为全球最流行的编程语言之一。在Java领域&#xff0c;Java高级工程师是一个非常有前途的职业&#xff0c;随着互联网和移动应用的不断发展&#xff0c;Java高级工程师的需求量也在不断增加。在这篇文章中&#…

Node.js 学习系列(二) —— 创建一个应用

Node.js 应用由三部分组成&#xff1a; &#xff08;一&#xff09;require 指令&#xff1a; 在 Node.js 中&#xff0c;使用 require 指令来加载和引入模块&#xff0c;引入的模块可以是内置模块&#xff0c;也可以是第三方模块或自定义模块。 语法格式&#xff1a; cons…

Qt+QtWebApp开发笔记(一):QtWebApp介绍、下载和搭建基础封装http轻量级服务器Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/130631547 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

MySQL学习---16、触发器

1、触发器 MySQL从5.0.2版本开始支持触发器。MySQL的触发器和存储过程一样&#xff0c;都是嵌入到MySQL服务器的一段程序。 触发器是由某个事件来触发某个操作&#xff0c;这些事件包括Insert、Update、Delete事件。所谓事件就是指用户的动作或者触发某项行为。如过定义了触发…

杂记——24.HTML中空格的写法

前几天写项目时&#xff0c;突然对HTML中空格的写法感兴趣&#xff0c;于是搜了一下&#xff0c;现在对其进行总结 HTML不是一种编程语言&#xff0c;而是一种超文本标记语言 (markup language)&#xff0c;是网页制作所必备的。超文本”就是指页面内可以包含图片、链接&#…

PDF文件转换工具Solid Converter PDF 10.1版本在Win10系统的下载与安装配置教程

目录 前言一、Solid Converter PDF安装二、使用配置总结 前言 Solid Converter PDF是一种PDF文件转换工具&#xff0c;可以将PDF文件转换为Microsoft Word、Excel、PowerPoint等格式。它还支持批量转换和OCR&#xff08;光学字符识别&#xff09;功能。 Solid Converter PDF的…

NIO基础

NIO 在学习Netty之前&#xff0c;我们需要先了解一下NIO&#xff0c;以便更好的学习Netty NIO是non-blocking io&#xff0c;也就是非阻塞IO 1.三大组件 1.1 channel & Buffer channel 有一点类似于 stream&#xff0c;它就是读写数据的双向通道&#xff0c;可以从 ch…

【YOLO系列】YOLO v3(网络结构图+代码)

文章目录 网络结构YOLO v3YOLOv3-SPP 多尺度预测损失函数参考 最近在研究YOLO系列&#xff0c;打算写一系列的YOLO博文。在YOLO的发展史中&#xff0c;v1到v3算法思想逐渐完备&#xff0c;后续的系列也都以v3为基石&#xff0c;在v3的基础上进行改进&#xff0c;所以很有必要单…