当前位置: 首页 > article >正文

【MyBatis 源码拆解系列】MyBatis 运行原理 - 读取 xml 配置文件

欢迎关注公众号(通过文章导读关注:【11来了】),持续 分享大厂系统设计!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

MyBatis 源码系列文章:
(一)【MyBatis 源码拆解系列】MyBatis 源码如何学习?

MyBatis 运行原理

MyBatis 源码的解析已经有很多了,这里不再重复去写,可以参考开源的 MyBatisDemo,文中示例代码位于 MyBatisDemo - 示例 3

  • 参考源码示例:https://github.com/yeecode/MyBatisDemo

先来介绍一下 MyBatis 运行原理,来对它底层的运行机制有一个整体上的把握,之后再根据各个功能深入去看是如何实现的

MyBatis 初始化使用代码如下:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {

        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            // 1、读取 xml 配置文件
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 2、得到 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);

        // 3、创建 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 4、通过 SqlSession 获取对应 Mapper
            UserMapper userMapper = session.getMapper(UserMapper.class);
            
            User userParam = new User();
            userParam.setSchoolName("Sunny School");
            // 5、调用接口展开数据库操作
            List<User> userList =  userMapper.queryUserBySchoolName(userParam);
            
            for (User user : userList) {
                System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
            }
        }
    }
}

在 SpringBoot 中使用 MyBatis,只需要将数据库的配置放在 yml 文件中,再去写好 Mapper 接口和对应的 Mapper.xml 文件就可以使用了,这是因为引入了 mybatis-spring-boot-starter 的 maven 依赖,这些加载工作都通过 SpringBoot 来自动帮我们完成了

而这里只引入了 MyBatis 的 maven 依赖,因此需要自己去读取 MyBatis 的 xml 配置文件,并且进行一些初始化操作,来执行我们在 Mapper.xml 中定义 SQL 语句

接下来会将这 5 个步骤拆分进行介绍

一、MyBatis 读取 xml 配置文件

MyBatis 源码系列文章:

  • (一)MyBatis 源码如何学习?

本节主要介绍流程如下:

image-20240922143233261

先看第一部分 xml 配置文件初始化,要加载 xml 配置,肯定是需要读取 xml 文件并进行解析,再将 xml 文件中的配置项加载到当前对应的对象中,MyBatis 是如何做的呢?

这里为了清晰简洁,只列举部分代码:

// DemoApplication
inputStream = Resources.getResourceAsStream(resource);

先通过 MyBatis 源码 io 包下的 Resources 类将执行的 xml 文件读取为 inputStream

// MyBatis 源码内部 io 包下的 Resource 类
public static InputStream getResourceAsStream(String resource) throws IOException {
  return getResourceAsStream(null, resource);
}

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;
}

适配器模式:

这里涉及了 适配器模式 ,对于 getResourceAsStream(ClassLoader loader, String resource) 方法有两个参数,但是有些调用方只传一个参数,那么就写一个方法对其他未传的参数进行适配 getResourceAsStream(String resource) ,这里就是对 ClassLoader 传入 null 做适配

最终走到 ClassLoaderWrappergetResourceAsStream(resource, loader) 来加载对应的 xml 文件

// MyBatis 源码内部 io 包下的 ClassLoaderWrapper 类
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
  return getResourceAsStream(resource, getClassLoaders(classLoader));
}

这里最终读取 xml 文件是通过 ClassLoader 来读取,这里通过 getClassLoaders(classLoader) 方法去获取对应的类加载器

类加载器:

ClassLoader 就是 Java 语言自身的类加载器,作用是 动态加载类(即 .class 文件),同样 xml 文件也可以加载为输入流(InputStream)

不同类型的 ClassLoader 加载资源的位置也是不同的

// MyBatis 源码内部 io 包下的 ClassLoaderWrapper 类
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
  return new ClassLoader[]{
      classLoader,
      defaultClassLoader,
      Thread.currentThread().getContextClassLoader(),
      getClass().getClassLoader(),
      systemClassLoader};
}

如上,该方法返回了 ClassLoader 数组,为什么要返回多个 ClassLoader 呢?

为了确保在多种环境下都可以加载到对应的资源,并且这里返回的 ClassLoader 数组是有一个先后顺序的,从前向后遍历 ClassLoader 数组,只要有一个 ClassLoader 成功加载资源就返回该资源

这里参数传入的 classLoader 在第一个,因此他的优先级最高;systemClassLoader 在最后一个,因此他的优先级最低

扩展:

如果我们也需要实现一个资源加载的优先顺序,也可以参考类似的方式来完成

// MyBatis 源码内部 io 包下的 ClassLoaderWrapper 类
InputStream getResourceAsStream(String resource, 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;
}

如上,获取了 ClassLoader 数组之后,就会遍历类加载器来完成资源的加载,核心代码就是 cl.getResourceAsStream(resource) ,这里会根据 ClassLoader 类型的不同走到具体不同的实现中

这里通过 debug 发现传入的 ClassLoader 数组都是 AppClassLoader,因此进入 AppClassLoader 中可以看到内部如何加载 resource 资源

image-20240921002221817

AppClassLoader 继承自 URLClassLoader,最终走到 URLClassLoader 中,这里具体深层的代码就不细看了,在 getResource() 方法内部,类加载器加载资源涉及到 双亲委派 ,是 JVM 中的内容,也就是找到 parent(父类加载器)去加载对应资源,如果父类加载器都加载不到再自己去加载

public class URLClassLoader extends SecureClassLoader implements Closeable {
    
    public InputStream getResourceAsStream(String name) {
        // Jvm 的双亲委派:会找 parent 也就是父类加载器去完成资源的加载,如果都加载不到,再自己去加载
        URL url = getResource(name);
        // ... 省略
    }
}

总结:

  • 配置文件的读取是通过 JVM 的 类加载器 ClassLoader 来读取的

  • 读取配置文件的方法最终封装在了 ClassLoaderWrapper 类中,该类会返回 ClassLoader[] 数组,以及遍历类加载器去读取文件,并且通过 ClassLoader[] 数组中类加载器的顺序来控制加载的优先级

  • MyBatis 源码中讲 io 相关的操作全部放在了 io 包下,包括 Resources、ClassLoaderWrapper 类都位于 MyBatis 的 io 包下,按照类的功能进行目录之间的划分,这样的好处是需要看哪个功能的代码,去该包下可以快速找到对应的所有类

    包括在 DDD 中,核心也是领域划分,领域之间独立、低耦合,每个领域高度内聚,更方便研发人员梳理和分析对应领域代码

    image-20240921154414640

http://www.kler.cn/a/316249.html

相关文章:

  • 执行flink sql连接clickhouse库
  • sol机器人pump机器人如何实现盈利的?什么是Pump 扫链机器人?
  • 【2024软考架构案例题】你知道 Es 的几种分词器吗?Standard、Simple、WhiteSpace、Keyword 四种分词器你知道吗?
  • 微服务(二)
  • Appium配置2024.11.12
  • 机器学习——贝叶斯
  • 怎么在PPT中嵌入字体?2个做ppt的实用技巧,不容错过!
  • 深入解读 iVector:语音识别与说话人识别领域的关键技术
  • 食品安全群众满意度调查流程
  • 基于Python的自然语言处理系列(14):TorchText + biGRU + Attention + Teacher Forcing
  • 2016年国赛高教杯数学建模D题风电场运行状况分析及优化解题全过程文档及程序
  • 【python】字面量
  • 计算机毕业设计之:基于uni-app的校园活动信息共享系统设计与实现(三端开发,安卓前端+网站前端+网站后端)
  • Redis主从架构原理
  • sensitive-word 敏感词 v0.20.0 数字全部匹配,而不是部分匹配
  • Hive企业级调优[2]—— 测试用表
  • 生动好看的css卡片样式
  • pytorch 48 基于voc2012数据集对yolov10s模型进行剪枝尝试
  • 【JavaEE初阶】文件IO(上)
  • 阿里云kafka消息写入topic失败
  • 【Nginx】Nginx 使用 SSL 的详细指南
  • 内核是如何发送数据包
  • Python国产新 ORM 框架 fastzdp_sqlmodel 快速入门教程
  • AI时代保持并提升自身的核心竞争力
  • 常⻅中间件漏洞(WebLogic)靶场
  • 树莓派pico上手