MyBatis提供了插件功能,也就是拦截器功能,可以让我们在已映射语句执行过程中的某一点进行拦截调用。常用的插件就是ibatis3-spring-support包中的com.ibatis3.dialect.OffsetLimitInterceptor。我们先看看插件的实现然后再学习下OffsetLimitInterceptor。
1.1 实现Interceptor接口,接口代码如下:
Object intercept(Invocation invocation) throws Throwable 就是实现要增强的功能;
Object plugin(Object target);包装目标类,其实就是生成代理对象;
void setProperties(Properties properties); 获取插件自定义的参数;
此处的实现我只是打印了参数,并没有做别的处理,实现类上的注解是用于告诉程序我要拦截那些组件的那些方法,目前支持的组件和方法如下:
1.2配置
请注意标签的顺序,顺序不对会报错的,上篇博客中有说明。
2.1通过重写Object intercept(Invocation invocation) throws Throwable方法,我们可以对正在执行的方法进行修改,但是这个修改一定要慎重,因为涉及到底层的修改,官方建议要特别当心。
2.2重写该Object plugin(Object target)方法,实现Plugin.wrap(target, this),源码如下:
该方法使用的是jdk的动态代理模式,也就是根据接口来代理,所以此处有获取type所有的接口的代码:Class>[] interfaces = getAllInterfaces(type, signatureMap);signatureMap是我们配置的需要拦截的对象的方法的集合。先看看getSignatureMap方法
该方法主要是获取当前拦截器的注解标记中配置的拦截相关信息,比如拦截的对象,方法,及参数等,此处大量使用了jdk的反射功能,大致流程如下:
先判断是否有@Intercepts注解,没有抛异常,有则获取该注解下的所有的签名注解,进而获取该签名注解配置的所有方法,然后返回一个map,该map的key是签名注解的type。此处值得记住的api是有:
接下来我们看看getAllInterfaces:
获取目标对象的类的所有接口,然后判断该接口是指定的要拦截的类,则添加到接口集合中,最后返回该集合。此处需要注意的方法是
接下来我们看看
也就是返回一个指定接口代理类的实例,它会将方法的执行分发到指定的执行处理器。第三个参数InvocationHandler h就是执行的处理器,Plugin就实现了InvocationHandler接口。该接口和方法签名如下:
/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*
现在我们看看plugin的实现:
该方法就是从当前的签名map中查看当前要执行的方法的是否是声明的要拦截的方法,如果是则执行拦截器中的方法,不是则直接执行原方法。interceptor.intercept(new Invocation(target, method, args));就是我们声明的拦截器中的方法。Invocation只是一个简单的封装而已,但是这个处理让代码更加优美结构化,至于结构的设计后续会学习,此处不赘述。
MyBatis在启动的过程中会注册所有的拦截器到拦截器链InterceptorChain中,请看源码:
然后在执行过程中需要需要创建Executor ,ParameterHandler ,ResultSetHandler ,StatementHandler 时都会进行plugin,如果执行的方法在拦截方法范围内则进行代理类的生成包装。
该方法在生成默认的sqlsession时会被调用,
StatementHandler在执行executor中的方法时会调用configuration中的newStatementHandler,而newStatementHandler中会将生成的StatementHandler实现类进行包装处理。
剩余两个组件类似。
根据配置的方言数据库类,生成对应的方法对象,并设置到该拦截器中。
4.2 拦截处理
具体逻辑不详细分析,只是看到去调了各自关于limit的实现。
可以看看mySql数据库的实现:
再看看oracle数据库的实现,
其余的大家可以自行查看。
5.1 要在类名上添加@Intercepts注解,该注解表示当前类是一个拦截器的插件。
5.2 在@Intercepts注解内添加数组形式的@Signature,该@Signature表示要拦截的方法签名的描述。
示例如下:
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class})
type代表了要拦截的类型,也就是以上提到的四个类型Executor ,ParameterHandler ,ResultSetHandler ,StatementHandler 例如Executor .class;
method 代表了该类型对应的方法,例如query,此方法必须是于对应的类型相匹配;
args代表了要执行的方法的参数,因为里面有甚多方法都是由重载的,所以要求执行参数。
我的例子中配置如下
其要拦截的就是执行器的查询和update方法,参数也指定了,当程序运行的时候,就会在执行我拦截的方法时,对其进行拦截处理。
该实现使用了代理模式,反射的的知识。