MyBatis源码解析【7】接口式编程

前言

这个分类比较连续,如果这里看不懂,或者第一次看,请回顾之前的博客

http://www.cnblogs.com/linkstar/category/1027239.html

修改例子

在我们实际中我们常见的一种模式就是只是书写mybatis的接口,而并不做mybatis的实现,从而减少了代码量和一些没有必要的错误。

下面我们继续修改之前的例子。

只需要修改我们的主要测试类就可以了

public class MainTest { public static void main(String[] args) throws Exception { SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilderTest.getSqlSessionFactory(); SqlSession session = sqlSessionFactory.openSession(); DemoMapper demoMapper = session.getMapper(DemoMapper.class); Demo demo = demoMapper.selectDemo(); System.out.println(demo.getValue()); /*try { Demo demo = (Demo) session.selectOne("com.xex.dao.mapper.DemoMapper.selectDemo"); System.out.println(demo.getValue()); } finally { session.close(); }*/
    }
}

从例子中我们可以看到,在mybatis中,一个没有实现类的接口可以正常的被调用,并且直接执行到相对应的sql语句。

那么mybatis是如何实现的呢?

这个问题就是我们今天需要学习的重点了。

大致运行步骤

我们依旧先从大的方向看

DemoMapper demoMapper = session.getMapper(DemoMapper.class);

首先是从我们的产品sqlsession中调用了一个getMapper传入了一个需要被调用接口的类

这个类的传入类型这边会产生疑问,为什么会传入这样一个class类型呢?

调用完成之后直接返回了一个接口,又会有疑问,难道这个接口在这个方法里面被new出来了吗?

接下来就是接口直接调用方法了。

Demo demo = demoMapper.selectDemo();

调用的方法似乎看起来和原来我们写的普通的接口没有什么不同

但是这个接口并没有实现类!!!

那么执行为什么会返回结果呢?

这个结果又是如何封装的呢?

带着这些问题我们在仔细往里面看看。

源码解析

首先进入了DefaultSqlSession

MyBatis源码解析【7】接口式编程

MyBatis源码解析【7】接口式编程进去

MyBatis源码解析【7】接口式编程

然后一直往里走,

MyBatis源码解析【7】接口式编程

这里的代码就很神奇了

一般人到这里就看不懂了(如果你的装备还不够好的话)

首先mapperProxyFactory是什么?

从名字上面我们把它叫做映射代理工厂。

knownMappers这个呢是一个map

这个map是以class作为键,代理工厂作为值的一个map

然后第一步就是从这个map中取到我们的代理工厂

------------------------------------------------------

对于map哪里来的,map里面的值是从哪里来的?是在mybatis读取xml配置文件的时候加载的,具体我不仔细一步步看这些加载的代码因为对于我们当前的最终目的关系不大,有兴趣的可以根据下面的思路去,从

new SqlSessionFactoryBuilder().build(inputStream);开始看看配置文件加载的过程。

1、build中的parser.parse()

2、parseConfiguration

3、mapperElement(root.evalNode("mappers"));

4、String mapperClass = child.getStringAttribute("class");

5、else if (resource == null && url == null && mapperClass != null) {
           Class<?> mapperInterface = Resources.classForName(mapperClass);
           configuration.addMapper(mapperInterface);
6、addMapper一直进去就有咯

------------------------------------------------------

我们先看看这个代理工厂有什么用?

return mapperProxyFactory.newInstance(sqlSession);

这个newInstance的名称是不是熟悉?没错就是反射里面的那个。我们进去看看

MyBatis源码解析【7】接口式编程

final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);

我们先看MapperProxy这个构造方法,传入了三个参数,sqlSession,接口和一个map

然后构造方法内部成员变量进行赋值就构造完成了我们这个代理类。

然后就是重点了。

(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

看到这个是不是又有熟悉的感觉了?没错这个是动态代理的知识。

第一个参数是:类加载器

第二个参数是:接口数组

第三个参数是:代理实例的调用处理程序

由动态代理的知识我们可以知道,传入的代理类代理了这个接口。

也就是说,当这个接口的方法被调用的时候,都会先调用代理类中的invoke方法。

如果不明白为什么,证明你还需要好好学习一下动态代理的知识哦。

然后我们进入MapperProxy类(代理类)

MyBatis源码解析【7】接口式编程

很显然这个类实现了InvocationHandler接口

重写了invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   try {
     if (Object.class.equals(method.getDeclaringClass())) {
       return method.invoke(this, args);
     } else if (isDefaultMethod(method)) {
       return invokeDefaultMethod(proxy, method, args);
     }
   } catch (Throwable t) {
     throw ExceptionUtil.unwrapThrowable(t);
   }
   final MapperMethod mapperMethod = cachedMapperMethod(method);
   return mapperMethod.execute(sqlSession, args);
}

第一个参数:代理实例(不需要管,没用到)

第二个参数:当我们调用接口方法时,因为有代理类所以会调用invoke方法同时将调用的是什么方法传入进来。

第三个参数:是方法调用时的参数

首先我们要注意Object.class.equals(method.getDeclaringClass())

通过method.getDeclaringClass()获取的class就是我们传入的class,肯定不是object所以不可能去执行if内部的逻辑,也就是说,不会也不能调用原来方法。因为没有实现类。

原本的动态代理,在代理实例中的invoke最后我们经常见到的method.invoke();我们可以在这个方法执行的前后加入自己的逻辑,而这次mybatis之所以不能调用这个方法,是因为这个接口没有实现类所以不需要调用,直接走自己的逻辑,而自己的逻辑就是下面两行代码。

final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);

首先是cachedMapperMethod这个方法是返回了一个MapperMethod

MyBatis源码解析【7】接口式编程

我们来看看这个类是如何构造的

首先传入了一个接口mapperInterface

一个方法method

配置文件sqlSession.getConfiguration

MyBatis源码解析【7】接口式编程

然后根据这三个参数又创建了一个SqlCommand的类

this.command = new SqlCommand(config, mapperInterface, method);

这个类的构造方法就比较重要了,仔细看

MyBatis源码解析【7】接口式编程

MyBatis源码解析【7】接口式编程

首先通过接口的名称+方法名就构成了statementName

然后从配置文件中去寻找这个statementName

而这个东西我们写xml就应该意识到就是xml的id

那么我们就可以知道,这里所做的事情就是将我们的接口与我们的xml进行匹配绑定

也就是为什么我们执行一个对应的接口方法的时候就可以找到对应xml里面执行sql的原因了。

而且这里最后从配置文件中拿到了MappedStatement之后就从这个类中可以拿到

name = ms.getId();
type = ms.getSqlCommandType();

也就是运行的名称也就是ID和sql的类型,增删改查

MyBatis源码解析【7】接口式编程

然后之前除了SqlCommand还有一句

this.method = new MethodSignature(config, mapperInterface, method);

这里是在干什么呢?

进去之后就会发现一大堆的赋值,但是有一点很明显

this.returnType = method.getReturnType();

也就是说,这里构造的所谓的MethodSignature叫方法签名,就是通过传入的接口调用的方法获取返回值类型。这里要记住哦,下面还有用的。

然后我们再来看看前面两句中的后面一句,执行

MyBatis源码解析【7】接口式编程

return mapperMethod.execute(sqlSession, args);

execute中首先就是根据刚才获取的类型,看看是哪一种的类型的语句

MyBatis源码解析【7】接口式编程

我们这里是查询

这里就用到为了我们刚才所初始化的方法签名method,通过这里对返回值类型的判断,从而就能执行对应的方法了。

如果你的返回值是list那么就会调用selectList

如果你的返回值是一个那么就会调用selectOne等等

总之,返回值的不同而导致了最后调用的不同,也就最终执行了我们之前所讲过的sql语句了。

以上就是为什么我们通过getMapper方法获取接口之后,直接调用接口就可以获取到返回数据的原因了。

总结

1、明确了MyBatis面向接口式编程的全部原理。

2、整个过程有点长,如果对于代理模式和反射掌握的不熟悉的话推荐直接debug模式跟踪断点。

3、之后会尝试写一个例子来模拟整个过程让整个过程更加清晰一些。

上一篇:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码


下一篇:【MyBatis源码解析】MyBatis一二级缓存