mybatis源码分析:mybatis中的动态代理使用的一些思考

Scroll Down

概述

mybatis中使用动态代理技术来实现当用户只提供接口时,mybatis提供对相应接口的实现的能力。这里之所以提供该能力主要的目的是通过该框架来将整体的各种数据库操作高度规范化,而不是给用户暴露相关接口,做到在操作数据库流程方面也就是对于封装JDBC实现高度的规范化。

MapperProxyFactory

首先MapperProxyFactory的主要作用有两个方面一是用来获取对于用户接口通过动态代理技术实现之后的用户接口的实现类。二是对相应的实现类的方法调用提供了缓存的功能,这里的缓存主要是为了节省每次计算方法执行的SQL类型及方法元数据信息操作。

  1. 构建实现类
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
  1. 缓存实现类的方法调用
// 缓存实现类的方法
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

MapperProxy

该类的作用是实现对接口中的方法进行调用时的执行逻辑的封装。也就是实现了对非hashcode、equals等方法的调用逻辑。MapperProxy类实现了InvocationHandler接口,并对invoke方法进行了实现。
在MapperProxy中对于不同类型的方法进行了不同的处理,定义了MapperMethodInvoker接口用于实现对不同类型的方法执行的抽象。
这里定义了两个实现类分别是:PlainMethodInvoker和DefaultMethodInvoker。PlainMethodInvoker类用于描述对用户定义的方法进行调用操作。DefaultMethodInvoker用于描述对Lookup类型的方法的调用操作。(还没有研究这种调用方式)
以PlainMethodInvoker类为例,在MapperProxy的具体方法调用操作逻辑中,mybatis通过MapperMethod来对不同的方法类型进行不同的调用逻辑操作。

MapperMethod

MapperMethod是用来描述用户定义的接口中的方法的。主要标记这个方法的sql类型,以及方法的一些基本信息,比如方法的参数名,返回值类型等内容。
代码如下:

// 方法对应的SQL类型,增删改查中的一种
private final SqlCommand command;
// 方法的基本信息
private final MethodSignature method;

SqlCommand的字段如下:

// 这里的name代表用户在mapper中定义的id
private final String name;
// 执行的类型
// UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
private final SqlCommandType type;

在PlainMethodInvoker的invoke方法中对调用MapperMethod的execute方法。具体实现代码如下:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据不同的SQL类型执行不同的逻辑
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          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;
}

convertArgsToSqlCommandParam是用来将方法的参数名和实际值转化成一个Map,供SqlSession来使用。
最后使用SqlSession来执行相应的操作。