一、Mybatis的插件实现原理
Mybatis允许用户通过自定义拦截器的方式改变Sql的执行方式,例如在Sql执行时追加Sql分页语法,从而达到减缓分页查询的目的,用户自定义拦截器也被称为Mybatis插件。
我们先从插件的配置及解析过程来分析插件,在Mybatis的主配置文件中,可以通过<plugins>标签注册用户自定义的插件信息,例如:
mybatis的插件实际上就是一个拦截器,Configuration类中维护了一个interceptorChain实例,代码如下:
interceptorChain是一个拦截器链,用于存放<plugins>标签注册的所有拦截器,Configuration类中还定义了一个addInterceptor方法,用于向拦截器链中添加拦截器,mybatis框架在应用启动时会对<plugins>标签进行解析,下面是XMLConfigBuilder类的pluginElement方法解析<plugins>标签的过程。
到此我们知道了拦截器的解析以及注入,下面我们看看拦截器是怎么执行的?
用户自定义的插件只能对mybatis的四种组件的方法进行拦截,这四种组件及方法如下:
为什么mybatis插件能够对这四种组件的实例进行拦截?
我们可以从源码中获取答案,前面介绍Configuration组件的作用时,我们了解到Configuration组件有三个作用:
mybatis使用工厂方法创建这四个组件,其中一个原因是可以根据用户配置的参数创建不同实现类的实例,还有一个就是可以在工厂方法中执行拦截逻辑,我们可以看看这些工厂方法的实现:
可以看出这些工厂方法都调用了InterceptorChain对象的pluginAll方法,该方法会返回对应组件的代理对象,拦截逻辑就是在代理对象中完成的,这就是为什么Mybatis自定义插件只能对这四个组件的方法进行拦截的原因。
好的,我们现在回到拦截链InterceptorChain类本身来,该类的实现如下:
该interceptorChain类中通过一个List对象维护所以的拦截器实例,在interceptorChain的pluginAll方法里面,会调用所有拦截器实例的plugin方法,调用完后会返回一个代理对象。
Mybatis中所有用户自定义的插件都必须实现Interceptor接口,该接口的定义如下:
为了方便用户创建四个组件的代理对象,mybatis提供了一个Plugin工具类,其关键代码如下:
如上面所示,Plugin类实现了InvocationHandler接口,即采用了JDK内置的动态代理方式创建代理对象,Plugin维护了四个组件的实例、自定义的拦截器实例、拦截方法等。Plugin类的invoke方法会在调用目标对象的方法时被调用, 在invoke方法中首先判断该方法是否被Intercepts注解指定为被拦截的方法,如果是则调用用户自定义的拦截器的intercept方法,并将目标方法等信息封装成Invocation对象作为参数。
Plugin类还提供了一个静态wrap方法,该方法用于简化动态代理对象的创建:
Intercepts注解用于修饰拦截器类,告诉拦截器要对哪些组件的方法进行拦截,下面是使用案例:
上面的例子,就是通过Intercepts注解指定拦截就是指定 拦截Executor组件的query方法、StatementHandler组件的prepare方法。
接下来我们看看如何进行解析@Intercepts注解的,解析方法为getSignatureMap:
我们自定义一个插件时,只需要实现Interceptor接口,在Intercept方法中编写拦截逻辑,然后在plugins方法中返回一个动态代理对象,在setProperties方法设置<plugin>标签中配置的属性,例如下面的例子:
我们基本只需要在Intercept方法中定义拦截逻辑就完成大部分工作。
最后我们再来回顾下Mybatis插件的工作原理,以执行一个查询操作为例:
1、获取动态代理对象
2、执行拦截逻辑
=========================== 自定义插件 ==============================
二、自定义一个分页插件
三、自定义慢查询sql统计插件
aaaaq