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

news/2024/11/20 10:21:54/

动态代理 invoke 方法

  1. 问题
  • mapperProxy.findByCondition(1); 是怎么完成的增删改查操作?
  1. 当通过 JDK 代理方式生成代理对象后,可以通过代理对象执行代理方法
public class MybatisTest {/*** 问题3:mapperProxy.findByCondition(1); 是怎么完成的增删改查操作?* 解答:invoke()--->根据sqlCommandType值来判断是要进行增删改查那种操作--->查询:再判断返回值类型-->sqlSession里面的方法**/@Testpublic 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;...@Overridepublic 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) 返回值是 PlainMethodInvokerreturn 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. 当前执行的方法对应的 Classfinal Class<?> declaringClass = method.getDeclaringClass();// 3. 获取对应的 MappedStatementMappedStatement 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;}@Overridepublic 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.ppmy.cn/news/513571.html

相关文章

联想平板功耗模块经验分享

联想平板功耗模块流程 首先联想平板的开发过程大致分为EVT&#xff0c;DVT1&#xff0c;DVT2&#xff0c;PVT, MP阶段&#xff0c;PVT阶段之后意味着需要code frezzing,然后工厂集中生产&#xff0c;最后发货出售&#xff0c;EVT&#xff0c;DVT&#xff0c;PVT阶段每个阶段工…

看看EPSON怎么造墨水

EPSON墨水工厂位于日本长野县广丘陵&#xff0c;距离名古屋机场大约4小时路程。接待我们的是日本爱普生事业部的平原精一与喷墨打印机事业部部长三村孝雄。整个访问分记者问答与工厂实地参观两个部分。 EPSON墨水工厂位于日本长野县广丘陵&#xff0c;距离名古屋机场大约4小时路…

强化学习:AI领域的下一步里程碑

第一章&#xff1a;引言 近年来&#xff0c;人工智能&#xff08;AI&#xff09;的快速发展引起了全球范围内的广泛关注。在AI的众多技术领域中&#xff0c;强化学习&#xff08;Reinforcement Learning&#xff09;作为一种类似于人类学习的方式&#xff0c;在解决复杂问题方…

闪亮登场!在树莓派上点亮LED灯的简单详细方法

文章目录 树莓派开发与STM32开发的比较原理图以及树莓派引脚展示点灯步骤读取树莓派布局 树莓派开发与STM32开发的比较 树莓派和STM32都是常用的嵌入式设备&#xff0c;都可以使用GPIO来控制LED灯。它们的点灯方式和使用的编程语言以及开发环境略有不同: 相同点&#xff1a; 控…

<C++项目>高并发内存池

项目介绍&#xff1a; 原型是goole的开源项目tcmalloc(全称:Thread-Caching Malloc),用于替代系统的内存分配相关的函数(malloc, free).知名度非常高。 项目要求知识储备和难度&#xff1a; 会用到C/C、数据结构(链表、哈希桶)、操作系统内存管理、单例模式、多线程、互斥锁等等…

RabbitMQ学习总结

目录 一、第一章 1、pom依赖 二、第二章 1、消息属性对象(Delivery delivery) 2、信道对象 (发送消息根据路由发送&#xff0c;接收消息根据队列接收) 3、工作队列模式 4、消息应答 (消费者) 5、消息自动重新入队 (消费者) 6、RabbitMQ持久化 (生产者) 7、不公平分发(…

借用jQuery发送Http请求的实现(内附源码)

文章目录 一、前言二、jQuery 介绍三、jQuery 下载四、jQuery 使用五、Http客户端DEMO实现 一、前言 最近在解决项目上前同事开发的视频播放器问题&#xff0c;视频播放器是用Qt开发&#xff0c;作为播放插件供Web调用。 播放器与Web的通信方式采用的是Http&#xff0c;数据格…

品优购商城——手机详情页(作业)

效果图&#xff1a; 手机详情页文件 detail.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>手机详情页-综合网购首选-正品低廉,品质保障,配送及时,轻松购物!</title><meta name"…