这个分类比较连续,若是这里看不懂,或者第一次看,请回顾以前的博客html
http://www.cnblogs.com/linkstar/category/1027239.htmlsql
在咱们实际中咱们常见的一种模式就是只是书写mybatis的接口,而并不作mybatis的实现,从而减小了代码量和一些没有必要的错误。编程
下面咱们继续修改以前的例子。数组
只须要修改咱们的主要测试类就能够了session
} }
从例子中咱们能够看到,在mybatis中,一个没有实现类的接口能够正常的被调用,而且直接执行到相对应的sql语句。 mybatis
那么mybatis是如何实现的呢? app
这个问题就是咱们今天须要学习的重点了。ide
咱们依旧先从大的方向看学习
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);开始看看配置文件加载的过程。
{
一、build中的parser.parse()
二、parseConfiguration
三、mapperElement(root.evalNode("mappers"));
四、String mapperClass = child.getStringAttribute("class");
五、else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
六、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方法获取接口以后,直接调用接口就能够获取到返回数据的缘由了。
一、明确了MyBatis面向接口式编程的所有原理。
二、整个过程有点长,若是对于代理模式和反射掌握的不熟悉的话推荐直接debug模式跟踪断点。
三、以后会尝试写一个例子来模拟整个过程让整个过程更加清晰一些。