MyBatis源码学习之四 插件plugin

插件

    MyBatis提供了插件功能,也就是拦截器功能,可以让我们在已映射语句执行过程中的某一点进行拦截调用。常用的插件就是ibatis3-spring-support包中的com.ibatis3.dialect.OffsetLimitInterceptor。我们先看看插件的实现然后再学习下OffsetLimitInterceptor。

  1 怎么用

    1.1 实现Interceptor接口,接口代码如下:


Object intercept(Invocation invocation) throws Throwable 就是实现要增强的功能;

Object plugin(Object target);包装目标类,其实就是生成代理对象;

void setProperties(Properties properties); 获取插件自定义的参数;



此处的实现我只是打印了参数,并没有做别的处理,实现类上的注解是用于告诉程序我要拦截那些组件的那些方法,目前支持的组件和方法如下:


1.2配置

扫描二维码关注公众号,回复: 2373133 查看本文章


请注意标签的顺序,顺序不对会报错的,上篇博客中有说明。

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:


获取目标对象的类的所有接口,然后判断该接口是指定的要拦截的类,则添加到接口集合中,最后返回该集合。此处需要注意的方法是

接下来我们看看


Proxy是jdk反射包中的类,该方法的说明是


也就是返回一个指定接口代理类的实例,它会将方法的执行分发到指定的执行处理器。第三个参数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只是一个简单的封装而已,但是这个处理让代码更加优美结构化,至于结构的设计后续会学习,此处不赘述。


3.执行流程:

MyBatis在启动的过程中会注册所有的拦截器到拦截器链InterceptorChain中,请看源码:


然后在执行过程中需要需要创建Executor ,ParameterHandler ,ResultSetHandler ,StatementHandler 时都会进行plugin,如果执行的方法在拦截方法范围内则进行代理类的生成包装。


该方法在生成默认的sqlsession时会被调用,


StatementHandler在执行executor中的方法时会调用configuration中的newStatementHandler,而newStatementHandler中会将生成的StatementHandler实现类进行包装处理。



剩余两个组件类似。

4.OffsetLimitInterceptor

    OffsetLimitInterceptor该插件根据名字判断是一个处理查询指定记录的拦截器,因为不同的数据库在实现指定记录的语法上时有区别的,例如mysql时,语法是select * from db_table where clasue limit n1,n2  但是其他的数据库不是这个样子的,这个时候我们就需要在不同的数据库执行限量查询的时候根据数据库修改执行的sql,该插件就是这个功能。
4.1 设置方言数据库类型

根据配置的方言数据库类,生成对应的方法对象,并设置到该拦截器中。

4.2 拦截处理


具体逻辑不详细分析,只是看到去调了各自关于limit的实现。


可以看看mySql数据库的实现:


再看看oracle数据库的实现,


其余的大家可以自行查看。

5.拦截签名如何配置

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方法,参数也指定了,当程序运行的时候,就会在执行我拦截的方法时,对其进行拦截处理。

6.总结

    该实现使用了代理模式,反射的的知识。

猜你喜欢

转载自blog.csdn.net/tony_java_2017/article/details/80804409
今日推荐