『手撕 Mybatis 源码』06 - Mapper 代理方式初始化

news2024/10/7 17:29:50

Mapper 代理方式初始化

  1. 首先修改一下 SqlSession 获取代理对象方式,即通过 getMapper() 来拿到动态代理对象
public class MybatisTest {
  /**
   * 问题1:<package name="com.itheima.mapper"/> 是如何进行解析的?
   * 解答:解析得到 name 属性的值(包名)-->根据包名加载该包下所有 的mapper 接口--->将 mapper 接口及代理工厂对象存到 knownMappers map 集合中
   *      根据 mapper 接口的路径替换. / ---根据替换后路径定位到对应的映射配置文件--->XMLMapperBuilder.parse() 注解方式解析
  **/
  @Test
  public void test2() throws IOException {

    // 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1) 解析了配置文件,封装 configuration 对象 (2)创建了 DefaultSqlSessionFactory 工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3.问题:openSession()执行逻辑是什么?
    // 3. (1) 创建事务对象 (2)创建了执行器对象cachingExecutor (3) 创建了DefaultSqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4. JDK动态代理生成代理对象,就是这里
    UserMapper mapperProxy = sqlSession.getMapper(UserMapper.class);
    ...
  }
}
  • 修改 sqlMapConfig.xml 引入配置文件的方式
<configuration>

  ...
  <!--第二部分:引入映射配置文件-->
  <mappers>
    <package name="com.itheima.mapper"/>
  </mappers>
</configuration>
  • UserMapper.xml 放到和 com.itheima.mapper.UserMapper 同一个目录,同时修改一下命名空间,然后就可以学习 MyBatis 的代理方式
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 即修改这里 -->
<mapper namespace="com.itheima.mapper.UserMapper">

  <cache></cache>
  <select id="findByCondition" resultType="com.itheima.pojo.User" useCache="true">
    SELECT id, name FROM  user WHERE id = #{id}
  </select>
</mapper>
  1. 问题
  • <package name=“com.itheima.mapper”/> 是如何进行解析的?
  1. 首先解析配置文件还是从 SqlSessionFactoryBuilderbuild() 开始,方法内会先创建 XMLConfigBuilder,然后开始解析 sqlMapConfig.xml 配置文件
public class SqlSessionFactoryBuilder {
  
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // XMLConfigBuilder:用来解析XML配置文件
      // 使用构建者模式(至少 4 个以上成员变量):好处:降低耦合、分离复杂对象的创建
      // 1. 创建 XPathParser 解析器对象,根据 inputStream 解析成了 document 对象 2. 创建全局配置对象 Configuration 对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

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

    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  // 4. 返回对应封装的 DefaultSqlSessionFactory`
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}
  1. parse() 会继续先解析 /configuration 节点,然后从根节点开始找到每个节点进行解析
public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private final XPathParser parser;
  ...
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;

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

    return configuration;
  }
}
  1. 这次我们更关注解析 /mappers 这个基点
public class XMLConfigBuilder extends BaseBuilder {
  ...
  private void parseConfiguration(XNode root) {
    try {
      ...
      // 1. 解析 </mappers> 标签 加载映射文件流程主入口
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}
  1. 获取 /mappers 标签,然后遍历 /mappers 标签的子标签,也就是现在我们配置的 /package 标签,从标签中拿到 name 属性,即 com.itheima.mapper,然后开始将包下所有的 mapper 接口以及它的代理工厂对象存储到 Configuration 的一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
public abstract class BaseBuilder {
  protected final Configuration configuration;
  ...
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 1. 获取 <mappers> 标签的子标签
      for (XNode child : parent.getChildren()) {
        // 2. <package> 子标签
        if ("package".equals(child.getName())) {
          // 3. 获取 mapper 接口和 mapper 映射文件对应的 package 包名。即 com.itheima.mapper
          String mapperPackage = child.getStringAttribute("name");
          // 4. 将包下所有的 mapper 接口以及它的代理工厂对象存储到一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
          configuration.addMappers(mapperPackage);
        } else {// <mapper>子标签
        ...
      }
    }
  }
}
  1. Configuration 对象会交由 MapperRegistry 来添加到 Map 集合
public class Configuration {
  ...
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  ...
  public void addMappers(String packageName) {
    // 1. 交由 MapperRegistry 来添加到 Map 集合。其中 packageName 为 com.itheima.mapper
    mapperRegistry.addMappers(packageName);
  }
}
  1. MapperRegistry 先创建解析工具类 ResolverUtil,根据 packageName 找到该包下所有的 Mapper 接口文件,然后将 Mapper 接口添加到 MapperRegistry
public class MapperRegistry {
  ...
  public void addMappers(String packageName, Class<?> superType) {
    // 其中 packageName 为 com.itheima.mapper,而 superType 为 Object.class
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 1. 根据 package 名称,加载该包下 Mapper 接口文件(不是映射文件)
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 2. 获取加载的 Mapper 接口,其实就是从 matches 拿到类集合
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 3. 将 Mapper 接口添加到 MapperRegistry 中
      addMapper(mapperClass);
    }
  }
}
  • ResolverUtil 找接口时,先把 packageName 改成 com/itheima/mapper,然后查找所有的 .class 文件,然后加载这个文件存入 matches
public class ResolverUtil<T> {
  
  private Set<Class<? extends T>> matches = new HashSet<>();
  ...
  protected String getPackagePath(String packageName) {
    // 1. 修改报名
    return packageName == null ? null : packageName.replace('.', '/');
  }
 
  public ResolverUtil<T> find(Test test, String packageName) {
    // 2. packageName 为 com.itheima.mapper,改成 com/itheima/mapper
    String path = getPackagePath(packageName);

    try {
      List<String> children = VFS.getInstance().list(path);
      for (String child : children) {
        if (child.endsWith(".class")) {
          // 3. 添加到 matches 属性中
          addIfMatching(test, child);
        }
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }
    return this;
  }
  
  protected void addIfMatching(Test test, String fqn) {
    try {
      // 4. 把 fqn 的 com/itheima/mapper/UserMapper.class 改全类名,即 com.itheima.mapper.UserMapper
      String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
      ClassLoader loader = getClassLoader();
      if (log.isDebugEnabled()) {
        log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
      }
      // 5. 加载类
      Class<?> type = loader.loadClass(externalName);
      if (test.matches(type)) {
        // 6. 将类装入 matches 中
        matches.add((Class<T>) type);
      }
    } catch (Throwable t) {
      log.warn("Could not examine class '" + fqn + "'" + " due to a "
          + t.getClass().getName() + " with message: " + t.getMessage());
    }
  }
}
  • 得到类集合之后,先判断对应的类是否已经加入过 knownMappers,如果是,则抛出异常,否则就加入 knownMappers 中,其中 key 为 type,value 为 MapperProxyFactory 代理工厂,如果接口中有使用 @Select 或者配置了 Mapper.xml,就需要使用 MapperAnnotationBuilder 进一步解析
public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  ...
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      // 1. 如果 Map 集合中已经有该 mapper 接口的映射,就不需要再存储了
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 2. 将 mapper 接口以及它的代理对象存储到一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // 3. 用来解析注解方式的 mapper 接口
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 4. 解析注解方式的 mapper 接口
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}
  • 解析 Mapper.xml 时,先获取 mapper 接口的全路径(即 interface com.itheima.mapper.UserMapper)。如果 Configuration 对象从来没加载过,就创建 XMLMapperBuilder 来解析。然后遍历 type 的每个方法,都解析构成 MappedStatement 对象,存入 Configuration 对象中
public class MapperAnnotationBuilder {
  
  private final Configuration configuration;
  private final MapperBuilderAssistant assistant;
  private final Class<?> type;
  ...
  public void parse() {
    // 1. 获取 mapper 接口的全路径,即 interface com.itheima.mapper.UserMapper
    String resource = type.toString();
    // 2. 是否解析过该 mapper 接口
    if (!configuration.isResourceLoaded(resource)) {
      // 3. 先解析 mapper 映射文件
      loadXmlResource();
      // 设置解析标识
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      // 解析CacheNamespace注解
      parseCache();
      // 解析CacheNamespaceRef注解
      parseCacheRef();
      // 遍历接口的每个方法
      for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
          continue;
        }
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {
          parseResultMap(method);
        }
        try {
          // 4. 每个 mapper 接口中的方法,都解析成 MappedStatement 对象
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }
  
  void parseStatement(Method method) {
    // 1. 获取 Mapper 接口的形参类型
    final Class<?> parameterTypeClass = getParameterType(method);
    ...
    // 2. 添加到 Configuration 对象
    assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          statementAnnotation.getDatabaseId(),
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    });
  }
}
  1. 总结
    在这里插入图片描述

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

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

相关文章

算法刷题-数组-有序数组的平方

977.有序数组的平方 力扣题目链接 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#…

clang到底是什么?gcc和clang到底有什么区别?

最近发现自己对 GNU GCC 和 Clang 的区别不太清楚&#xff0c;影响到一些实现和学习&#xff0c;所以趁这两天有空好好研究了一下。 在这个研究过程中&#xff0c;我发现很多问题其实源自于语言&#xff08;不是指编程语言&#xff0c;而是中文和英文翻译的失真&#xff09;和…

前端前端学习不断

卷吧卷吧...&#xff0c;这东西什么时候是个头啊……

智能指针(2)

智能指针&#xff08;2&#xff09; shared_ptr(共享型智能指针)基础知识特点引用计数器共享型智能指针结构理解 shared_ptr仿写删除器类计数器类shared_ptr类使用以及仿写代码的理解 循环引用_Weaks 初始化智能指针的方法 shared_ptr(共享型智能指针) 基础知识 在java中有一…

chatgpt赋能python:Python如何判断输入的字符——基础教程与实例

Python如何判断输入的字符——基础教程与实例 时至今日&#xff0c;互联网已经成为人们获取信息的重要途径&#xff0c;而搜索引擎优化&#xff08;SEO&#xff09;则是网站重要的推广手段之一。而Python作为一种高级编程语言&#xff0c;在实现SEO时也有很大的优势&#xff0…

chatgpt赋能python:Python如何进行升序和降序排列

Python如何进行升序和降序排列 Python是一种非常流行的编程语言&#xff0c;由于其在数据科学、机器学习和人工智能等领域的强大表现&#xff0c;越来越多的人开始学习和使用Python。在Python中&#xff0c;排序是一项非常常见的操作。在这篇文章中&#xff0c;我将向您介绍如…

stable diffusion webui 登录接口(login)api接口调用(使用C#)

唠嗑 本次将跟读者讲一下如何通过C#请求sd webui api【login】接口&#xff0c;如果读者觉得文章有用&#xff0c;请给【点个赞】吧&#xff0c;有问题可以评论区提问。 实战 1.配置api启用参数 启动webui时&#xff0c;需加上【–api】 、【–api-auth 账号:密码】 和【–…

chatgpt赋能python:Python字符类型判断:如何判断字符是字母或数字

Python字符类型判断&#xff1a;如何判断字符是字母或数字 在Python编程中&#xff0c;经常需要判断一个字符是字母还是数字。本文将介绍如何在Python中判断字符类型&#xff0c;并给出几个示例。 判断字符类型的方法 在Python中&#xff0c;可以使用以下方法来判断字符类型…

chatgpt赋能python:Python中如何删除变量中的字符

Python中如何删除变量中的字符 在Python编程中&#xff0c;我们有时需要清除变量中的字符。删除字符可以是去掉字符串中的某些字符&#xff0c;也可以是从列表或元组中删除某些元素。本文将介绍Python中如何删除变量中的字符。 删除字符串中的字符 Python使用字符串的切片操…

网络安全入门学习第十五课——PHP基础

文章目录 一、WEB技术1、什么是web2、B/S架构3、C/S架构 二、PHP概述1、PHP是什么2、PHP受欢迎的原因3、基于MVC模式的PHP框架4、常用编译工具5、PHP环境搭建6、开发工具 三、PHP基本语法格式1、标记2、输出语句3、注释4、标识符 四、数据与运算1、常量1.1、常量定义1.2、预定义…

前端vue实现页面加水印文字 单个页面所有页面加水印 水印颜色

前端vue实现页面加水印文字, 可以实现系统所有页面加水印,也可以单个页面加水印, 可更改水印颜色, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin?id12889 效果图如下: #### 使用方法 使用方法 /* 给系统所有页面加水印*/ // 第一个参数:水印…

Shell脚本函数简介及运用

目录 一、函数的作用 二、定义函数 三、调用函数 1.在脚本中调用函数 2.在函数中调用函数 四、函数传参 五、函数的返回值 六、函数的递归 七、函数及其变量的作用范围 八、外部脚本调用函数 一、函数的作用 语句块定义成函数约等于别名&#xff0c;定义函数&#xf…

【云原生网关】Apache ShenYu 使用详解

目录 一、前言 二、Apache ShenYu 介绍 2.1 为什么叫ShenYu 2.2 ShenYu特点 2.3 ShenYu架构图 2.4 shenyu数据同步原理 2.4.1 Zookeeper数据同步原理 三、Apache ShenYu 安装部署 3.1 部署流程 3.1.1 创建 Docker Network 3.1.2 拉取Apache ShenYu Admin镜像 3.1.3…

C语言之函数栈帧的创建与销毁(2)

上一篇博客我们讲到了函数栈帧的创建与销毁&#xff08;1&#xff09;今天我们来讲解Add函数的函数栈帧相关知识 在开始本章博客之前&#xff0c;大家可以把上一篇博客的主要内容仔细复习一下 看图 第一个mov&#xff1a;把b的值放到eax里面去 第二个mov&#xff1a;把a的…

【python】【excel】用excel中指定单元格的内容去替换一个文本中指定的字符

1 使用背景 理正的.spw文件是文本格式&#xff0c;类似于该软件的前处理&#xff0c;相关参数字段可通过文本替换&#xff0c;快速修改参数。 后续用途可用在&#xff1a;用EXCEL整理数据&#xff0c;通过修改文本批量获取多个截面参数的spw文件 2 ExcelSheet-shift-textstr…

macOS中解决matplotlib中文乱码

现象 图表上中文变方框&#xff0c;日志中报错如下&#xff1a; findfont: Generic family sans-serif not found because none of the following families were found: 解决办法 下载字体 http://xiazaiziti.com/210356.html 查询字体保存路径 查看配置文件路径 import…

01- 数据类型(C语言)

一 变量和常量 1.1 标识符 1、在我们所写的“第一个C程序”中出现了很多的标识符&#xff0c;例如include、main、printf、return。标识符是⽤来标识变量、函数&#xff0c;或任何其他⽤户⾃定义项⽬的名称。 2、标识符的约束规范&#xff1a; 只能包含数字、字母和下划线不…

chatgpt赋能python:Python怎么删除列表

Python怎么删除列表 什么是Python列表&#xff1f; 在Python中&#xff0c;列表是一个可变的序列&#xff0c;它可以包含不同类型的数据。列表可以使用中括号 [] 来定义&#xff0c;每个元素之间用逗号分隔。列表中的元素可以通过其下标进行访问&#xff0c;下标从0开始。 P…

Skywalking基础使用

Skywalking基础使用 agent的使用Linux下Tomcat7和8中使用Spring Boot中使用RocketBot的使用 agent的使用 agent探针可以让我们不修改代码的情况下&#xff0c;对java应用上使用到的组件进行动态监控&#xff0c;获取运行数据发送到OAP上进行统计和存储。agent探针在java中是使…

C语言之数组初级(5-8)

目录 1. 一维数组的创建和初始化 2. 一维数组的使用 3. 一维数组在内存中的存储 4. 二维数组的创建和初始化 5. 二维数组的使用 6. 二维数组在内存中的存储 7. 数组越界 8. 数组作为函数参数 二维数组的画图讲解 例如现在我要找第二行第三列这个元素&#xff0c;下面是…