• MyBatis源码解读(3)——MapperMethod


    在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法。为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理。

    我相信在初学MyBatis的时候几乎每个人都会发出一个疑问,为什么明明是XXXDao接口,我没有用任何代码实现这个接口,但却能直接使用这个接口的方法。现在清楚了,动态代理。我们来写一个demo小程序来看看。

    首先是一个Test.java的接口,只有一个say方法。

     1 package day_16_proxy;
     2 
     3 /**
     4  * @author 余林丰
     5  *
     6  * 2016年11月16日
     7  */
     8 public interface Test {
     9     void say();
    10 }

    我们现在想像MyBatis那样不用实现它而是直接调用。

    test.say();

    当然不可能直接实例化一个接口,此时就需要生成一个代理类TestProxy.java,这个类需实现InvocationHandler接口。

     1 package day_16_proxy;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 
     6 /**
     7  * @author 余林丰
     8  *
     9  * 2016年11月16日
    10  */
    11 public class TestProxy implements InvocationHandler {
    12 
    13     /* (non-Javadoc)
    14      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
    15      */
    16     @Override
    17     public Object invoke(Object proxy, Method method, Object[] args)
    18             throws Throwable {
    19         if (method.getName().equals("say")){
    20             System.out.println("hello world");
    21         }
    22         return null;
    23     }
    24 
    25 }

    需要实现invoke方法,意思就是“调用”的意思(当然我们想要调用接口中的方法时并不会显示调用invoke)。我们从第19行看到,当调用的方法是say时,输出“hello world”。有了这个TestProxy.java代理类过后,我们再来客户端代码中测试。

     1 package day_16_proxy;
     2 
     3 import java.lang.reflect.Proxy;
     4 
     5 /**
     6  * @author 余林丰
     7  *
     8  * 2016年11月16日
     9  */
    10 public class Client {
    11 
    12     public static void main(String[] args){
    13         Test test = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[]{Test.class}, new TestProxy());
    14         test.say();
    15     }
    16 }

    在第14行代码中,我们已经能够直接调用Test接口中的say方法了,原因就在于我们通过Proxy.newProxyInstance方法生成了一个代理类实例即TestProxy。

    回到我们的MyBatis源码,在上一节中我们知道了一个Dao接口实际上是通过MapperProxyFactory生成了一个MapperProxy代理类。

    1 //org.apache.ibatis.binding.MapperProxyFactory
    2 protected T newInstance(MapperProxy<T> mapperProxy) {
    3   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    4 }

    第3行代码是不是和在开头的例子中客户端测试代码如出一辙?生成代理类,这个代理类就是MapperProxy。清楚了MyBatis是如何构造出代理类的算是解决了第一个问题——一个接口怎么能直接调用其方法。

    现在抛出第二个问题——接口中每个具体的方法是如何做到一一实现代理的呢?我们再来看看MapperProxy类。这次我们先看MapperProxy类实现的InvocationHanlder.invoke方法。

     1 //org.apache.ibatis.binding.MapperProxy
     2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     3   if (Object.class.equals(method.getDeclaringClass())) {
     4     try {
     5       return method.invoke(this, args);
     6     } catch (Throwable t) {
     7       throw ExceptionUtil.unwrapThrowable(t);
     8     }
     9   }
    10   final MapperMethod mapperMethod = cachedMapperMethod(method);
    11   return mapperMethod.execute(sqlSession, args);
    12 }

    第3行做一个判断,判断是否是一个类,如果是一个类,那么久直接传递方法和参数调用即可。但我们知道此时是一个接口(也可以自己实现接口,旧版本通常这样做)。如果不是一个类的话,就会创建一个MapperMethod方法。见名思意:好像就是这个类在执行我们所调用的每一个接口方法。最后返回的是MapperMethod.execute方法。暂时不予理会MapperProxy类中的cachedMapperMethod方法。

    来看看MapperMethod类,这个MapperMethod类就不得了了啊,可以说它是统管所有和数据库打交道的方法(当然概括起来也只有insert、delete、update、select四个方法)。所以,不管你的dao层有多少方法,归结起来的sql语句都有且仅有只有insert、delete、update、select,可以预料在MapperMethod的execute方法中首先判断是何种sql语句。

    //org.apache.ibatis.binding.MapperMethod
    public Object execute(SqlSession sqlSession, Object[] args) {
        switch (command.getType()) {
            case INSERT: {……}
            case UPDATE: {……}
            case DELETE: {……}
            case SELECT: {……}
            case FLUSH: {……}
        }
    }

    这是MepperMethod.execute方法的删减,我们可以看到确实在execute方法内部首先判断是何种sql语句。(注意:在阅读这部分源代码时,我们的主线是MyBatis是如何创建出一个代理类,以及实现其方法的,而暂时忽略其中的细节)

    我们选择常见的"SELECT"sql语句来进行解读,而在"SELECT"语句中又会设计到较多的细节问题:

     1 //org.apache.ibatis.binding
     2 case SELECT:
     3         if (method.returnsVoid() && method.hasResultHandler()) {
     4           executeWithResultHandler(sqlSession, args);
     5           result = null;
     6         } else if (method.returnsMany()) {
     7           result = executeForMany(sqlSession, args);
     8         } else if (method.returnsMap()) {
     9           result = executeForMap(sqlSession, args);
    10         } else if (method.returnsCursor()) {
    11           result = executeForCursor(sqlSession, args);
    12         } else {
    13           Object param = method.convertArgsToSqlCommandParam(args);
    14           result = sqlSession.selectOne(command.getName(), param);
    15         }
    16         break;

    我们选取第7行中的executeForMany中的方法来解读试试看。

     1 //org.apache.ibatis.binding.MapperMethod
     2 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
     3   List<E> result;
     4   Object param = method.convertArgsToSqlCommandParam(args);
     5   if (method.hasRowBounds()) {
     6     RowBounds rowBounds = method.extractRowBounds(args);
     7     result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
     8   } else {
     9     result = sqlSession.<E>selectList(command.getName(), param);
    10   }
    11   // issue #510 Collections & arrays support
    12   if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    13     if (method.getReturnType().isArray()) {
    14       return convertToArray(result);
    15     } else {
    16       return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    17     }
    18   }
    19   return result;
    20 }

    第7行和第9行代码就是我们真正执行sql语句的地方,原来兜兜转转它又回到了sqlSession的方法中。在下一节,我们再重新回到重要的SqlSession中。

  • 相关阅读:
    Linux 命令笔记
    MySQL指令笔记
    悲观锁与乐观锁
    缓存在高并发场景下的常见问题
    死锁相关问题
    Java并发性和多线程
    Java同步和异步,阻塞和非阻塞
    内存溢出和内存泄漏
    JavaAndroid项目配置文件笔记
    Maven安装配置
  • 原文地址:https://www.cnblogs.com/yulinfeng/p/6072124.html
Copyright © 2020-2023  润新知