关于Mybatis插件,大部分人都知道,也都使用过,但不少时候,咱们仅仅是停留在表面上,知道Mybatis插件能够在DAO层进行拦截,如打印执行的SQL语句日志,作一些权限控制,分页等功能;但对其内部实现机制,涉及的软件设计模式,编程思想每每没有深刻的理解。编程
本篇案例将帮助读者对Mybatis插件的使用场景,实现机制,以及其中涉及的编程思想进行一个小结,但愿对之后的编程开发工做有所帮助。设计模式
注:本案例以mybatis 3.4.7-SNAPSHOT版本为例。mybatis
PS:文章是挺久以前写的,当时花了一些心思,存到电脑的word里,今天正好看到,就是里面的源码都是图片,哈哈哈,凑合着看吧。性能
mybatis的分页默认是基于内存分页的(查出全部,再截取),数据量大的状况下效率较低,不过使用mybatis插件能够改变该行为,只须要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句便可;单元测试
通常业务系统都会有建立者,建立时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上能够在DAO层统一拦截处理,能够用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值便可;测试
对于SQL语句执行的性能监控,能够经过拦截Executor类的update, query等方法,用日志记录每一个方法执行的时间;ui
其实mybatis扩展性仍是很强的,基于插件机制,基本上能够控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体能够根据项目业务来实现对应业务逻辑。插件
与其称为Mybatis插件,不如叫Mybatis拦截器,更加符合其功能定位,实际上它就是一个拦截器,应用代理模式,在方法级别上进行拦截。设计
那么这些类上的方法都是在什么阶段被拦截的呢?为理解这个问题,咱们先看段简单的代码(摘自mybatis源码中的单元测试SqlSessionTest类),来了解下典型的mybatis执行流程,以下代码所示:3d
以上代码主要完成如下功能:
以下是时序图,在整个时序图中,涉及到mybatis插件部分已标红,基本上就是体如今上文中提到的四个类上,对这些类上的方法进行拦截。
先来看下mybatis是如何加载插件配置的,对应的xml配置信息以下:
对应的解析代码以下,主要作如下工做:
以上逻辑对应的时序图以下:
Mybatis插件的实现机制主要是基于动态代理实现的,其中最为关键的就是代理对象的生成,因此有必要来了解下这些代理对象是如何生成的。
Executor代理对象
ParameterHandler代理对象
ResultSetHandler代理对象
StatementHandler代理对象
观察源码,发现这些可拦截的类对应的对象生成都是经过InterceptorChain的pluginAll方法来建立的,进一步观察pluginAll方法,以下:
遍历全部拦截器,调用拦截器的plugin方法生成代理对象,注意生成代理对象从新赋值给target,因此若是有多个拦截器的话,生成的代理对象会被另外一个代理对象代理,从而造成一个代理链条,执行的时候,依次执行全部拦截器的拦截逻辑代码;
接下来看一下咱们在编写拦截器的时候,一个典型的plugin方法实现方式,以下:
再进一步查看wrap方法,以下:
典型的动态代理实现,调用的是Proxy.newProxyInstance方法来生成代理对象。
以上逻辑对应的时序图以下,这里咱们假设声明了两个拦截器,那么在建立target代理对象的时候,最终返回的代理对象proxy2,实际上代理了proxy1,而proxy1又代理了target,:
因为真正去执行Executor、ParameterHandler、ResultSetHandler和StatementHandler类中的方法的对象是代理对象(建议将代理对象转为class文件,反编译查看其结构,帮助理解),因此在执行方法时,首先调用的是Plugin类(实现了InvocationHandler接口)的invoke方法,以下:
首先根据执行方法所属类获取拦截器中声明须要拦截的方法集合;
判断当前方法需不须要执行拦截逻辑,须要的话,执行拦截逻辑方法(即Interceptor接口的intercept方法实现),不须要则直接执行原方法。
能够关注下Interceptor接口的intercept方法实现,通常须要用户自定义实现逻辑,其中有一个重要参数,即Invocation类,经过改参数咱们能够获取执行对象,执行方法,以及执行方法上的参数,从而进行各类业务逻辑实现,通常在该方法的最后一句代码都是invocation.proceed()(内部执行method.invoke方法),不然将没法执行下一个拦截器的intercept方法。
以上逻辑对应的时序图以下,这里咱们以执行executor对象的query方法为例,且假设有两个拦截器存在:
这里以分页插件为例,来了解下通常mybatis插件的编写规则,以下所示:
主要须要实现三个方法
简单的说,mybatis插件就是对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四个接口上的方法进行拦截,利用JDK动态代理机制,为这些接口的实现类建立代理对象,在执行方法时,先去执行代理对象的方法,从而执行本身编写的拦截逻辑,因此真正要用好mybatis插件,主要仍是要熟悉这四个接口的方法以及这些方法上的参数的含义;
另外,若是配置了多个拦截器的话,会出现层层代理的状况,即代理对象代理了另一个代理对象,造成一个代理链条,执行的时候,也是层层执行;
关于mybatis插件涉及到的设计模式和软件思想以下:
一些注意事项: