前言

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

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

进去

然后一直往里走,

这里的代码就很神奇了

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

首先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的名称是不是熟悉?没错就是反射里面的那个。我们进去看看

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

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

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

然后就是重点了。

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

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

第一个参数是:类加载器

第二个参数是:接口数组

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

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

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

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

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

很显然这个类实现了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

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

首先传入了一个接口mapperInterface

一个方法method

配置文件sqlSession.getConfiguration

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

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

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

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

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

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

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

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

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

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

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

然后之前除了SqlCommand还有一句

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

这里是在干什么呢?

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

this.returnType = method.getReturnType();

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

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

return mapperMethod.execute(sqlSession, args);

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

我们这里是查询

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

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

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

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

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

总结

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

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

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

MyBatis源码解析【7】接口式编程的更多相关文章

  1. Mybatis源码解析5—— 接口代理

    本篇文章,可乐将为大家介绍通过接口代理的方式去执行SQL操作.话不多说,直接上图: 其实无论哪种方式,我们最终是需要找到对应的 SQL 语句,接口代理的方式就是通过 [包名.方法名] 的方式,去找到 ...

  2. Mybatis源码解析(一) —— mybatis与Spring是如何整合的?

    Mybatis源码解析(一) -- mybatis与Spring是如何整合的?   从大学开始接触mybatis到现在差不多快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,比如: 它是如 ...

  3. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  4. Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别

    XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...

  5. mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

    目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...

  6. mybatis源码-解析配置文件(三)之配置文件Configuration解析

    目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...

  7. Mybatis源码解析优秀博文

    最近阅读了许久的mybatis源码,小有所悟.同时也发现网上有许多优秀的mybatis源码讲解博文.本人打算把自己阅读过的.觉得不错的一些博文列出来.以此进一步加深对mybatis框架的理解.其实还有 ...

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

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

  9. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

  10. Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取

    在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...

随机推荐

  1. 2017-2018-2 20165315 实验四《Android程序设计》实验报告

    2017-2018-2 20165315 实验四<Android程序设计>实验报告 第24章:初识Android Android Studio项目的目录树 1 build:该目录包含了自动 ...

  2. thinkphp 视图(二)变量输出、赋值和替换

    view下的html文件会编译成php文件 编译的文件在runtime 下的temp目录 <p>{$email}</p> 会编译成 <?php echo $email; ...

  3. 【转载】Sql Server参数化查询之where in和like实现详解

    文章导读 拼SQL实现where in查询 使用CHARINDEX或like实现where in 参数化 使用exec动态执行SQl实现where in 参数化 为每一个参数生成一个参数实现where ...

  4. python07 函数式编程

    1.作用域 1.1  pass关键字表示,暂时不写该方法 1.2表示返回值为方法 输出结果333 1.3函数作用域:和函数调用没关系,和声明的位置有关系, 结果为444 2.匿名函数 lanmbda ...

  5. 设计模式 策略模式2 c++11

    根据需求的不同 选择不同的策略算法 之前是保存的各种策略类的指针 这里直接使用 function  bind 选择对应的算法 代码 // 005.cpp: 定义控制台应用程序的入口点. // #inc ...

  6. Element.querySelector和Element.querySelectorAll和jQuery(element).find(selector)选择器的区别

    <divid="test1"> <a href="http://www.hujuntao.com/">设计蜂巢</a> &l ...

  7. mui几种页面跳转方式对比

    1.初始化时创建子页面 mui.init({ subpages: [{ url: your - subpage - url, //子页面HTML地址,支持本地地址和网络地址 id: your - su ...

  8. mybatis 插入数据 在没有commit时 获取主键id

      <insert id="insert" parameterType="Mail" useGeneratedKeys="true" ...

  9. CUDA[3] Samples for accessing shared/global memory

    memory model: programming model: Source:  Udacity Class CS344

  10. python闭包和延迟绑定

    一.什么是闭包: 1.函数内定义函数. 2.外函数的返回时内函数的引用. 3.内函数使用外函数的局部变量(至少一个). 1 def outfunc(): 2 for num in range(4): ...