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

这篇具有很好参考价值的文章主要介绍了『手撕 Mybatis 源码』06 - Mapper 代理方式初始化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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. 总结
    『手撕 Mybatis 源码』06 - Mapper 代理方式初始化

文章来源地址https://www.toymoban.com/news/detail-491769.html

到了这里,关于『手撕 Mybatis 源码』06 - Mapper 代理方式初始化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言结构体的初始化方式

    逐个初始化字段 :这是最直接的方式,你可以逐个为结构体的每个字段进行初始化。 2.使用结构体字面值初始化 :这种方式允许你在初始化时使用一个字面值来为结构体提供初始值 3. 全局初始化 :在全局范围内,你可以在变量声明时就进行初始化。 4. 使用  memset  函数 :

    2024年02月09日
    浏览(42)
  • IDEA开发实现Maven+Servlet+Mybatis实现CRUD管理系统-Mapper代理开发

    之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下: 这里调用 selectList() 方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。 通过上面的描述可以看出 Mapper 代理方式的目的

    2024年02月05日
    浏览(33)
  • 【SA8295P 源码分析】86 - AIS Camera Device 设备初始化 之 AisProcChainManager 模块初始化源码分析

    【源码分析】 因为一些原因,本文需要移除, 对于已经购买的兄弟,不用担心,不是跑路, 我会继续持续提供技术支持, 有什么模块想学习的,或者有什么问题有疑问的, 请私聊我,我们 +VX 沟通技术问题,一起学习,一起进步 接下来,我一一私聊已经购买的兄弟添加V

    2024年02月10日
    浏览(30)
  • mybatis配置mapper-locations位置的多种方式

    yml 配置 pom.xml 配置 yml 配置 yml 配置

    2024年02月16日
    浏览(46)
  • 结构体声明、定义和初始化的几种方式

    五种结构体声明方式: 直接声明结构体类型 声明结构体类型的同时定义结构体变量 不指定结构体名而直接定义结构体变量 使用结构体标记和类型别名 直接声明结构体别名 在C语言中,标记(tag)是在定义struct, union或enum之后使用的标识符。 之所以称其为结构体的“

    2023年04月11日
    浏览(35)
  • QTextDocument的使用方法及几种初始化方式

    qtextDocument并不像控件一样可以通过拖拽方式拉到设计器中使用,也不能直接声明就可以初始化使用,而是需要通过包涵库文件的方式包涵在当前的窗体文件中。 如: 上面说了Qtextdocument并不是控件,它是辅助配合QT中的文本编辑控件进行文本操作的类库。 QTextDocument在使用之

    2024年02月09日
    浏览(37)
  • SpringMVC源码解析——DispatcherServlet初始化

    在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型的实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现Servlet接口的实现类。Servlet是一个JAVA编写的程序,此程序是基于HTTP协议的,在服务端运行的(如Tomcat),是按照Servlet规范

    2024年02月03日
    浏览(33)
  • canal server初始化源码分析

    在开始之前,我们可以先了解下, canal 配置方式 ManagerCanalInstanceGenerator: 基于manager管理的配置方式,实时感知配置并进行server重启 SpringCanalInstanceGenerator:基于本地spring xml的配置方式,对于多instance的时候,不便于扩展,一个instance一个xml配置 canal 配置文件 canal.properties  

    2024年01月19日
    浏览(34)
  • 基于Xml方式Bean的配置-初始化方法和销毁方法

    Bean的初始化和销毁方法配置 Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过 指定的方法名于自己创建的方法名一致即可 测试类 运行结果如下 我们还可以通

    2024年02月07日
    浏览(42)
  • 【Mybatis源码解析】mapper实例化及执行流程源码分析

    基础环境:JDK17、SpringBoot3.0、mysql5.7 储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》 基于SpringBoot的Mybatis源码解析: 1.如何对mapper实例化bean 在加载BeanDefinition时,会将SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer加载到注册表中,以供后续进行实例化。

    2024年02月01日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包