『手撕 Mybatis 源码』08 - 动态代理 invoke 方法

news2024/12/23 13:45:38

动态代理 invoke 方法

  1. 问题
  • mapperProxy.findByCondition(1); 是怎么完成的增删改查操作?
  1. 当通过 JDK 代理方式生成代理对象后,可以通过代理对象执行代理方法
public class MybatisTest {
  /**
   * 问题3:mapperProxy.findByCondition(1); 是怎么完成的增删改查操作?
   * 解答:invoke()--->根据sqlCommandType值来判断是要进行增删改查那种操作--->查询:再判断返回值类型-->sqlSession里面的方法
  **/
  @Test
  public void test2() throws IOException {
    ...
    // 1. 代理对象调用方法
    User user = mapperProxy.findByCondition(1);
    ...
  }
}
  1. 因为 mapperProxy 是个代理对象,所以会执行 invoke() 方法。对于 Object 的方法会直接跳过代理,否则会使用 PlainMethodInvoker 执行代理调用逻辑,同时从这里还能看出 MapperProxy 是实现了 InvocationHandler 接口,所以可以
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;
  ...
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 1. 如果是 Object 定义的方法,直接调用。过滤 Object 原生方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 2. 代理逻辑在这 cachedInvoker(method) 返回值是 PlainMethodInvoker
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
  
  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // methodCache,缓存(暂时不管)
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        // 接口的 default 方法
        if (m.isDefault()) {
          ...
        } else {
          // 3. public 方法的分支,构建了 PlainMethodInvoker。不太需要管为什么创建这个对象
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
}
  • 从上面代码 3 中看到,在执行 PlainMethodInvokerinvoke() 前,首先初始化构造 MapperMethod,特别要注意创建 SqlCommand 对象,这个属性等等会用到
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 1. 创建 SqlCommand,
    // 其中:
    //    mapperInterface:interface com.itheima.mapper.UserMapper
    //    method:public abstract com.itheima.pojo.User com.itheima.mapper.UserMapper.findByCondition(int)
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
  ...
}
  • SqlCommand 创建会获取当前调用的方法,以及对应的接口名称,通过接口名称 + 方法名称的方式,就能拿到 MappedStatement,从而得知 SqlCommand 的执行类型
 public static class SqlCommand {

   private final String name;
   private final SqlCommandType type;
   ...
   public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
     // 1. 当前调用的方法名称
     final String methodName = method.getName();
     // 2. 当前执行的方法对应的 Class
     final Class<?> declaringClass = method.getDeclaringClass();
     // 3. 获取对应的 MappedStatement
     MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
         configuration);
     if (ms == null) {
       if (method.getAnnotation(Flush.class) != null) {
         name = null;
         type = SqlCommandType.FLUSH;
       } else {
         throw new BindingException("Invalid bound statement (not found): "
             + mapperInterface.getName() + "." + methodName);
       }
     } else {
       // 4. 通过 MappedStatement 对象就能知道 sql 命令的执行类型
       name = ms.getId();
       type = ms.getSqlCommandType();
       if (type == SqlCommandType.UNKNOWN) {
         throw new BindingException("Unknown execution method for: " + name);
       }
     }
   }
}
  1. 完成初始化 MapperMethod 之后,就开始真正执行 PlainMethodInvokerinvoke() 方法,实际也是交由 MapperMethod 执行 execute() 方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
  ...
  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }
}
  1. MapperMethodexecute() 方法中,它会根据 SqlCommand 的方法类型,选择调用 SqlSession 的不同方法,对于 INSERT、UPDATE、DELETE 类型,其实最后都是执行 update 操作,SELECT 类型会根据方法返回的类型,执行不同的处理方法。根据我们的实验,最终是会执行查询单条 sqlSession.selectOne() 方法,并且会把结果返回
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;
  ...
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 1. 判断 mapper 中的方法类型
    switch (command.getType()) {
      // 添加
      case INSERT: {
        // 转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 最终调用的还是 sqlSession 中的方法
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      // 更新
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      // 删除。 对于增删改,都是update 操作
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      // 查询
      case SELECT:
         // 无返回结果,并且有 ResultHandler 方法参数,将查询结果交给 ResultHandler 进行处理
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
          // 执行查询、返回列表
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
          // 执行查询、返回 Map
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
          // 执行查询、返回 Cursor
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          // 转换参数
          Object param = method.convertArgsToSqlCommandParam(args);
          // 2. 查询单条
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
}
  1. 总结
    在这里插入图片描述

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

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

相关文章

Linux学习之rpm安装软件

包管理器是方便软件安装、卸载&#xff0c;解决软件依赖关系的重要工具。 CentOS、RedHat使用yum包管理器&#xff0c;软件安装包格式为rpm。 Debian、Ubuntu使用apt包管理器&#xff0c;软件安装包格式为deb。 rpm包格式&#xff1a;vim-common-7.4.629-8.el7_9.x86_64.rpm vi…

移植微雪例程-1.54寸双色水墨屏

微雪官网&#xff1a;E-Paper ESP32 Driver Board - Waveshare Wiki 去微雪官网&#xff0c;把墨水屏的例程下下来。 我这个使用的是1.54寸的双色墨水屏。 下载下来之后&#xff0c;然后移植到IDF上面。 移植epd1in54c-demo这个例程。 一.移植前的准备工作: 复制以下内容到…

扩展卡尔曼滤波在目标跟踪中的应用(1)

前面几节相继介绍的不同维度的卡尔曼滤波算法&#xff0c;在线性高斯模型的条件下&#xff0c;具有很好的跟踪效果&#xff0c;当时在实际生活中&#xff0c;不可能所有的运动都呈现一种线性关系&#xff0c;也存在着非线性关系&#xff0c;比如说&#xff1a;飞机的飞行状态&a…

DBA 从业者在行业的变革下 --- 可行性出路

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

玩转ChatGPT:基于Mucloud建立本地知识库

一、写在前面 人们普遍认为GPT有潜力颠覆教育行业&#xff0c;然而这种颠覆会以何种方式呈现呢&#xff1f; 在刘慈欣的科幻世界中&#xff0c;三体人拥有知识遗传的能力&#xff0c;这使得他们的技术迭代成本降至最低。然而&#xff0c;我们人类并未具备这样的特性&#xff…

mysql数据库学习日记第一天

一、简介 数据库&#xff08;DB&#xff09;&#xff1a;存储数据的仓库 数据库管理系统&#xff08;DBMS&#xff09;&#xff1a;数据库管理系统 SQL&#xff1a;操纵关系数据库的一种编程语言&#xff0c;定义一套关于操作关系数据库的一套标准 二、安装教程 到MYSQL官…

拼多多攻势凌厉,中小电商崛起,京东和阿里更换掌门人

日前阿里董事会主席兼CEO都由新的人士担任&#xff0c;此前京东也更换了CEO&#xff0c;两大电商掌门人更换&#xff0c;电商行业引发震动&#xff0c;这不免让人想起另一家正快速崛起的电商拼多多&#xff0c;业界人士指出拼多多的崛起让两大电商闪了腰。 一、拼多多和众多中小…

第十章 ShuffleNetv2网络详解

系列文章目录 第一章 AlexNet网络详解 第二章 VGG网络详解 第三章 GoogLeNet网络详解 第四章 ResNet网络详解 第五章 ResNeXt网络详解 第六章 MobileNetv1网络详解 第七章 MobileNetv2网络详解 第八章 MobileNetv3网络详解 第九章 ShuffleNetv1网络详解 第十章…

RISCV-semi host原理以及实践

嵌入式裸机调试需要在有限资源的目标硬件上尽可能挖掘更多的信息&#xff0c;比如打印寄存器等等&#xff0c;但是即便看似很简单的串口打印&#xff0c;在有的情况下也是奢望&#xff0c;针对这种情况&#xff0c;能够有效利用主机资源协同调试的semi-host&#xff08;半主机&…

基于YOLACT的目标跟踪算法移植与测试

基于YOLACT的目标跟踪算法移植与测试 一、初始化开发环境&#xff08;基于x86架构CPU的开发环境中完成&#xff09; 1.1 初始化开发环境 1.下载SOPHON SDK开发包 #下载SOPHON SDK wget https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/06/15/16/Release_230501-pub…

大数据就业路线

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 数仓&#x…

English Learning - L3 作业打卡 Lesson7 Day46 2023.6.19 周一

English Learning - L3 作业打卡 Lesson7 Day46 2023.6.19 周一 引言&#x1f349;句1: If your life were a book and you were the author, how would you want your story to go?成分划分弱读连读语调 &#x1f349;句2: That’s the question that changed my life foreve…

ps beta爱国版下载全过程

​前言&#xff1a; 前段时间也是写了一篇ps beta版本下载的全过程呀&#xff0c;当时那篇文章还没有将软件变成永久免费试用&#xff0c;所以ps beta只能进行7天的免费使用&#xff0c;很多朋友可能意犹未尽呀&#xff0c;所以现在继续&#xff0c;将软件变成永久免费使用​。…

步长(stride) | 填充(padding) | 扩长(dilation)

这几个名词中文真的好难翻译&#xff0c;不是大佬就不要造名词了&#xff0c;后面还是老老实实用英文吧&#xff01;&#xff08;标题是机翻的 。&#xff09; stride stride 很好理解&#xff0c;stride 就是卷积核移动的步长。 如下图&#xff1a; stride1 stride2 paddi…

【嘉立创EDA】复合焊盘的引线居中对齐,typc接口引线对齐方法

文章路标👉 文章解决问题主题内容小结文章解决问题 1️⃣ 嘉立创EDA中对齐工具越发完善,但一些特殊的器件引线中,由于对齐属性设置等原因,部分引线无法与焊盘完全居中对齐,影响了线路的表象美观度与里内信号铺面。本文主要讲述如何应用嘉立创EDA的其他技巧将器件引线与…

基於Hadoop HA 在kerberos中配置datax

概要 提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 概要 前言一、基於HADOOP HA 搭建datax二、基於HADOOP HA 配置好的datax去配置kerberos1.在datax的配置文件中進行配置2.在shell腳本中加入認證語句 总结 前言…

detrex | 面向detr系列的目标检测开源框架

DETR作为Transformer应用于目标检测领域的开山之作&#xff0c;后续有大量的算法都是在其基础上改进而来&#xff0c;如Deformable-DETR&#xff0c;DAB-DETR&#xff0c;DN-DETR&#xff0c;DINO等。这些模型尽管都是采用DETR的基本架构&#xff0c;但其改进创新却各有千秋&am…

人工智能时代,你知道ai绘画如何使用吗?

在数字时代的今天&#xff0c;人工智能正逐渐渗透到我们生活的方方面面&#xff0c;艺术创作也不例外。ai绘画软件作为一种创新的工具&#xff0c;为艺术爱好者、设计师和创作者提供了崭新的创作方式。但是&#xff0c;对于初次接触这类软件的人来说&#xff0c;可能会感到一些…

EMC学习笔记(四)地的设计

地的设计 1.接地的含义2.接地目的3.基本的接地方式3.1 单点接地3.2 多点接地3.3 浮地3.4 以上各种方式组成的混合接地方式 4.关于接地方式的一般选取原则4.1 单板接地方式 接地是抑制电磁干扰、提高电子设备EMC性能的重要手段之一。正确的接地既能提高产品抑制电磁干扰的能力&a…

静音或取消静音?苹果AirPods17Beta版推出全新功能

近期&#xff0c;苹果的AirPods系列耳机推出了一些新功能&#xff0c;iOS 17 Beta版用户已可解锁尝试。 其中&#xff0c;第一项功能是静音或取消静音&#xff0c;适用于AirPods Pro&#xff08;第一代和第二代&#xff09;、AirPods&#xff08;第三代&#xff09;和AirPods …