MyBatis源码分析(Mapper动态代理的实现及执行流程)

首先简单回顾下代理模式

静态代理

概念:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

简单代码演示:

抽象接口:

真实角色:

代理角色:

测试使用:

动态代理

概念:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

在jdk的api中提供了java.lang.reflect.Proxy它可以帮助我们完成动态代理创建

注意:在java中使用Proxy来完成动态代理对象的创建,只能为目标实现了接口的类创建代理对象。

动态代理是在内存中直接生成代理对象。

InvocationHandler接口:

接口中声明了一个方法

Invoke方法,它是在代理对象调用行为时,会执行的方法,而invoke方法上有三个参数:

简单代码演示:

简单使用:

MyBatis中Mapper的动态代理

由上面的介绍可以看出,一般JDK的动态代理基本套路是这样:

但是在MyBatis中却看着好像不是一个“正宗”的动态代理,在Mybatis中被代理的类只有一个Mapper接口,这里取名为TestMapper,在MyBatis中是这样的:

我们定义了TestMapper,在MyBatis中通过配置,可以在Configuration中得到TestMapper,有MapperProxy,也建立了联系:

但是却没有TestMapper的实现类。

在上面的动态代理测试方法中:

至少是与TargetImpl差不多的。

但是在MyBatis中:

这个代理明显与IdCarfMapper完全不一样。结合动态代理的特性,可以猜测下次debug会进入invoke方法:

这里首先会比较当前调用的方法是不是来自Object类,意思就是比如当前的方法是toString()、hashCode()等这些来自Object的方法就直接走method.invoke()方法(这里也说明了框架作者考虑问题的严谨性);

然后回进一步判断isDefaultMethod(),代码注释如下,这里就不过多介绍了,判断的原因和上面差不多:

首先判断是不是default方法:

 private boolean isDefaultMethod(Method method) {
    return ((method.getModifiers()
             //不是抽象并且不是静态的 public方法
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
        //并且是接口
        && method.getDeclaringClass().isInterface();
  }

通过MethodHandles调用default方法:

private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {//获得 MethodHandles.Lookup 的构造器(Lookup私有构造器)
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    //创建一个 MethodHandles.Lookup 实例 
    //调用 代理对象的 接口方法 (default方法)
    return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

前面的两个健壮性判断完毕后就开始真正的执行SQL干正事了,很明显真正执行SQL方法是不会像之前的动态代理那样简单的method.invoke(),因为在这个invoke方法里面需要传入一个被代理对象,这里明显只有一个IdCardMapper接口,在MapperProxy中是没有接口实例的,而是会先执行cachedMapperMethod()方法,获取MapperMethod,其实cachedMapperMethod()方法也很简单:

 private MapperMethod cachedMapperMethod(Method method) {
    //先从methodCache中获取MapperMethod
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

而这个methodCache其实就是一个Map:

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  //methodCache就是一个Map
  private final Map<Method, MapperMethod> methodCache;

那么methodCache是从哪来的呢,其实就是构造的时候传递的一个引用:

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

而这个引用是从哪来的呢,其实就是工厂制造的时候传进去的:

 public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

那又是从哪来的呢,其实也就是一个Map:

/**
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

再来看看是怎么execute()的:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //这里没有使用策略模式,因为没必要
    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 {
          //将参数convert
          Object param = method.convertArgsToSqlCommandParam(args);
          //又回到了sqlSession,使用sqlSession执行
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }

再来看看selectOne()方法,其实也是比较简单的:

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    //会调用selectList()方法
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      //只返回一个,取第一个即可
      return list.get(0);
    } else if (list.size() > 1) {
      //这是MyBatis中很常见的一种异常
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

再看看selectList()方法:

 @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //委托,单一职责
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

通过上面流程可以看到MyBatis的Mapper执行SQL虽然用到了动态代理,但是不是那种“正规”的动态代理。

继续查看executor.query()方法,在这里出现了一点“事故”,先看看这个executor:

Executor是一个非常重要的接口:


其中BaseExecutor是一个抽象实现类,这里也使用了模板设计模式,这里将一些共性抽取到了BaseExecutor中,这个模板设计模式的使用也是值得学习的。

这里理应应该要执行的是由子类实现的query()方法:

但是进入的是Plugin类的invoke方法,这里又有一层代理:

最后实际执行的是一个Interceptor的拦截的一个方法:

一看到这个Interceptor,其实就恍然大悟了:


这不就是MyBatis的Plugins嘛,因为我的项目中配置了PageHelper,所以这里进入了PageHelper,不过这个也顺便说明了PageHelper是在执行语句之前执行的:

 

这里PageHelper不是主要的,以后再分析,为了避免干扰,先把PageHepler的引入和配置去掉。

去掉后,正常了,进入了org.apache.ibatis.executor.BaseExecutor#query,BaseExecutor是一个抽象类:

这里首先会获取CacheKey,在MyBatis中的CahcheKey设计的非常好,可以看下,这里就不过多介绍了。

这里的ErrorContext明显是ThreadLocal的一个封装,这个也是一个非常重要的东东,它对异常信息的封装便于使用此框架的人能够快速的排错:

会先走一级缓存,如果一级缓存没有拿到,会执行下面的方法:

这个方法里面有个很有意思的一个方法,localCache会先putObject()一下,这行代码的意义是声明一个占位符,当发送一次查询数据的请求时,设置该占位符告诉其他请求正在查询数据库,请其他请求先阻塞或休眠。当这次请求查询到数据之后,将真正的数据放到占位符的位置,缓存数据。如果其他请求与该次请求查询的数据时一样的,直接从一级缓存中拿数据减少了查询请求对数据库的压力 (org.apache.ibatis.executor.BaseExecutor.DeferredLoad#load org.apache.ibatis.executor.BaseExecutor.DeferredLoad#canLoad),接下来会执行doQuery()方法,doQuery()方法是BaseExecutor中的一个模板方法:

后面就是JDBC的执行流程了:

这里会有一个拦截器链去执行Plugins的拦截:

当sqlsessionFactory获取sqlsession时,产生的ParameterHandler、ResultSetHandler、StatementHandler、Executor都是由org.apache.ibatis.session.Configuration 类的方法 newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor产生的代理对象,而这些代理对象是经过适用规则的plugin层层代理的 。

最后会返回一个StatementHandler:

是不是和上面的Executor非常相似,也说明这个设计模式是多么的重要。

在prepareStatement()方法中,获取了Connection:

接下来会执行handler.<E>query(stmt, resultHandler):

而这个delegate也就是SatementHandler:

接下来看到了我们熟悉的PrepareStatement(是不是也说明MyBatis是防SQL注入的):

执行完成后会由ResultSetHandler处理结果:

再往下:

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    //又看到了熟悉的ErrorContext,不过这个activity的名称与之前的不一样
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

进入handlerResultSet方法:

首先会创建一个返回结果对象:

会执行org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)方法,首先会循环遍历为ResultMapping属性赋值。如果是嵌套查询而且配置了延迟加载,其中这里的createProxy()方法会生成一个具有延迟加载功能的代理对象:

在官方文档中对这里也有相关的说明:

这里还要注意的是,这里返回的是一个所有属性都为空的结果:

获取到返回对象后,就开始设值了:

MyBatis的关闭也是很有讲究的,ResultSet就会关闭与ResultSet相关的东东:

最后会执行:

只返回一个结果集就取第一个元素,否则全部返回:

最后代码再返回,会发现这里又会执行closeStatement:

这也是更加说明MyBatis是一层一层关闭的:

最后会将ErrorContext reset一下:

这个是没有异常的情况,如果有异常就会执行catch里面的内容:

如果出现Exception,一目了然:

©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页