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

动态代理 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. 总结
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值