Mybatis 插件开发

2019-07-22  本文已影响0人  茧铭

       之前跟踪mybatis执行源码的时候,结合一些网络资料。知道了这个执行过程中有四个非常重要的对象,这四个对象在Mybatis中都支持自定义插件进行拦截处理。

这个四个对象在创建的过程中都不是new对象然后就直接创建的,他们在创建之前,都有这么一句this.interceptorChain.pluginAll(target);。这个pluginAll的方法也是挺简单的,就是遍历所有的interceptor对象,用来包装传入的四大对象,并返回包装后的内容。

联想到了这儿,就可以知道插件的重点就是这个interceptor 拦截器。当前的这个Interceptor在Mybatis是一个接口,插件的实现第一步就是要实现这个接口。下面的例子是对内容的一些理解注释。

第一步就是写一个要实现了Interceptor接口的类,这个类的作用描述了如何包装四大对象,以及如何拦截内容。在 intercept() 方法中的invocation.proceed()的前后对数据进行更改,包括修改sql和参数等等操作都可以。

package com.huangyu.orderman.config;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Statement;
import java.util.Properties;


/**
 * type拦截对象,即StatementHandler 
 * method 拦截对象的方法,即parameterize方法
 * args是要拦截的参数,为了避免重载造成的影响,应该写将要拦截的方法的对应全部参数 ,这里就只有一个Statement 
 */
@Intercepts({
        @Signature(
            type= StatementHandler.class,method = "parameterize" ,args = Statement.class
        )
})
public class MybatisPluginInterceptor implements Interceptor {

    /**
     * 拦截目标对象的目标方法的执行
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // TODO 这里写入拦截处理的步骤

        /** 执行目标方法,即执行本来就要执行的执行sql语句。如果没有这一句,就相当于直接返回了,不再操作原本应该执行的逻辑了,会破坏mybatis的内部结构  */
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象,为目标创建一个代理对象
     *      即:将传入的四大对象包装一下,返回Proxy的代理对象。
     */
    @Override
    public Object plugin(Object target) {
        /**  Mybatis提供的包装对象Plugin */
        Object wrap = Plugin.wrap(target, this);

        /** 方法解析。getAllinterfaces(type, signatureMap) 这个方法在当前插件拦截器的注解中是否要拦截这个对象;
         *  比如说拦截器顶部的注解中要拦截Executor,interfaces.length才会大于0。否则就会跳过对Executor的包装
         *  包装代理对象也是利用Proxy代理对象处理,因此拦截之后会执行这个Plugin的invoke()的方法
         * public static Object wrap(Object target, Interceptor interceptor) {
                Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
                Class<?> type = target.getClass();
                Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
                return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
         }
         */

        return wrap;
    }

    /**
     * 获取插件注册传递的参数包装成Propereties并自动获取到
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println(properties);
    }
}

设计到具体对象的具体方法,应该怎么去拦截,就需要多读读其他插件的拦截机制,并且十分熟悉mybatis的执行流程,才能在对的地方去拦截方法修改参数。

最后呢,我们为了让这个插件能被使用到,需要将插件注册到全局配置文件mybatis-config.xml 的<plugin>的标签中

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
        <plugin interceptor="com.huangyu.orderman.config.MybatisPluginInterceptor">
            <property name="name" value="huangyu"/> <!-- 插件传递的参数 -->
        </plugin>
    </plugins>
</configuration>

结束之前,补充一下上面的Proxy的代理对象的内容。



Plugin代理对象包装内容之后,将原本要执行逻辑的四大对象,包装在了Object target中。代理对象执行了intercept() 方法后,再通过代码重点的 invocation.proceed() 方法执行原有的逻辑。
那如果有多个拦截器同时拦截这个对象的话,会多层级地去包装这个对象。那么可能出现的结构就是

 PluginProxy2{
     interceptor : interceptor 2
     target : PluginProxy1{
            interceptor : interceptor 1
            target : Executor
     }
}

这样就说明了,先被包装的拦截方法会后执行!因此在有一些处理关系的情况下,应该将越先执行的插件,在mybatis-config.xml中越后注册进去。

上一篇下一篇

猜你喜欢

热点阅读