千家信息网

Mybatis之插件原理

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,鲁春利的工作笔记,好记性不如烂笔头转载自:深入浅出Mybatis-插件原理Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重
千家信息网最后更新 2026年11月02日Mybatis之插件原理

鲁春利的工作笔记,好记性不如烂笔头



转载自:深入浅出Mybatis-插件原理


Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。


代理链的生成

Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。


下面以Executor为例。Mybatis在创建Executor对象时:

org.apache.ibatis.session.SqlSessionFactory.openSession()    org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession()        org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(execType, level, false) {            final Executor executor = configuration.newExecutor(tx, execType);        }

说明:org.apache.ibatis.builder.xml.XMLConfigBuilder类解析MyBatis的配置文件,获取其中配置的Interceptor并调用configuration.addInterceptor(interceptorInstance);配置到拦截器链中。


org.apache.ibatis.session.Configuration

package org.apache.ibatis.session;public class Configuration { protected Environment environment; protected String logPrefix;  protected Class  logImpl;  protected Class  vfsImpl;  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;  protected Set lazyLoadTriggerMethods = new HashSet(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));  protected Integer defaultStatementTimeout;  protected Integer defaultFetchSize;  // 默认的Executor  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;     // InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。   protected final InterceptorChain interceptorChain = new InterceptorChain();     public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    executorType = executorType == null ? defaultExecutorType : executorType;    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;    Executor executor;    if (ExecutorType.BATCH == executorType) {      executor = new BatchExecutor(this, transaction);    } else if (ExecutorType.REUSE == executorType) {      executor = new ReuseExecutor(this, transaction);    } else {      executor = new SimpleExecutor(this, transaction);    }    if (cacheEnabled) {      executor = new CachingExecutor(executor);    }    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;  }}

org.apache.ibatis.plugin.InterceptorChain

package org.apache.ibatis.plugin;/** * @author Clinton Begin */public class InterceptorChain {  private final List interceptors = new ArrayList();  /**   * 每一个拦截器对目标类都进行一次代理    * @param target    * @return 层层代理后的对象    */  public Object pluginAll(Object target) {    for (Interceptor interceptor : interceptors) {      target = interceptor.plugin(target);    }    return target;  }  public void addInterceptor(Interceptor interceptor) {    interceptors.add(interceptor);  }    public List getInterceptors() {    return Collections.unmodifiableList(interceptors);  }}

下面以一个简单的例子来看看这个plugin方法里到底发生了什么。

package com.invicme.common.persistence.interceptor;import java.sql.Connection;import java.lang.reflect.Method;import java.util.Properties;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.plugin.Intercepts;import org.apache.ibatis.plugin.Invocation;import org.apache.ibatis.plugin.Plugin;import org.apache.ibatis.plugin.Signature;import org.apache.ibatis.session.ResultHandler;import org.apache.ibatis.session.RowBounds;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** *  * @author lucl * @version 2017-01-09 *  * 示例的分页拦截器: * 
 *         @Intercepts用于表明当前的对象是一个Interceptor *         @Signature则表明要拦截的接口(type)、方法(method)以及对应的参数类型(args) *             type: Mybatis只支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。 *             method: 这几个类的方法 *             args:   这几个类的方法的参数 * 
* */@Intercepts(value = { @Signature(type=Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type=StatementHandler.class, method="prepare", args={Connection.class}) })public class SamplePagingInterceptor implements Interceptor { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 拦截方法 * Invocation中定义了一个proceed方法,其逻辑就是调用当前方法, * 如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。 */ @Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); logger.info("target is {}", target.getClass().getName()); Method method = invocation.getMethod(); logger.info("method is {}", method.getName()); Object[] args = invocation.getArgs(); for (Object arg : args) { logger.info("arg is {}", arg); } Object proceed = invocation.proceed(); logger.info("proceed is {}", proceed.getClass().getName()); return proceed; } /** * 用于封装目标对象,该方法可以返回目标对象本身,也可以返回一个它的代理对象。 */ @Override public Object plugin(Object target) { logger.info("target is {}", target.getClass().getName()); return Plugin.wrap(target, this); } /** * 用于在Mybatis配置文件中指定一些属性 */ @Override public void setProperties(Properties properties) { String value = properties.getProperty("sysuser"); logger.info("value is {}", value); }}

每一个拦截器都必须实现上面的三个方法,其中:

1) Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

2) Object plugin(Object target)就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的,把目标target和拦截器this传给了包装函数。

3) setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名 [type, method, args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。


代理链的生成

为了更好的理解后面的内容,穿插一个Java反射的示例

package com.invicme.common.aop.proxy;import java.lang.reflect.Method;import java.lang.reflect.Type;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.Map;import java.util.Set;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.plugin.Intercepts;import org.apache.ibatis.plugin.PluginException;import org.apache.ibatis.plugin.Signature;import org.apache.ibatis.session.Configuration;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.invicme.common.persistence.interceptor.SamplePagingInterceptor;/** *  * @author lucl * @version 2017-01-18 *  * 测试Class对象 * */public class MainDriver {    private static Logger logger = LoggerFactory.getLogger(MainDriver.class);    public static void main(String[] args) {        SamplePagingInterceptor interceptor = new SamplePagingInterceptor();        wrap(interceptor);    }        public static Object wrap (SamplePagingInterceptor interceptor) {        // 获取拦截器对应的@Intercepts注解        Map, Set> signatureMap = getSignatureMap(interceptor);        {            for (Iterator> it = signatureMap.keySet().iterator(); it.hasNext(); ) {                Class clazz = it.next();                Set set = signatureMap.get(clazz);                logger.info("【Signature】clazz name is {}", clazz.getName());                for (Iterator itt = set.iterator(); itt.hasNext(); ) {                    Method method = itt.next();                    logger.info("\tmethod name is {}", method.getName());                    Type[] genericParameterTypes = method.getGenericParameterTypes();                    for (Type type : genericParameterTypes) {                        logger.info("\t\tparam is {}", type);                    }                }            }        }                logger.info("================================================================");        {            Configuration configuration = new Configuration();            Executor executor = configuration.newExecutor(null);            Class[] interfaces = executor.getClass().getInterfaces();            for (Class clazz : interfaces) {                logger.info("【Interface】clazz name is {}", clazz);            }        }                return null;    }    /**     * 获取拦截器对应的@Intercepts注解     * @param interceptor     * @return     */    private static Map, Set> getSignatureMap(Interceptor interceptor) {        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);        // issue #251        if (interceptsAnnotation == null) {            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());        }        Signature[] sigs = interceptsAnnotation.value();        Map, Set> signatureMap = new HashMap, Set>();        for (Signature sig : sigs) {            // 处理重复值的问题            Set methods = signatureMap.get(sig.type());            if (methods == null) {                methods = new HashSet();                signatureMap.put(sig.type(), methods);            }            try {                Method method = sig.type().getMethod(sig.method(), sig.args());                methods.add(method);            } catch (NoSuchMethodException e) {                throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);            }        }        return signatureMap;    }}


继续进行Plugin代码的说明。

从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;import org.apache.ibatis.reflection.ExceptionUtil;/** * @author Clinton Begin */public class Plugin implements InvocationHandler {  private Object target;  private Interceptor interceptor;  private Map, Set> signatureMap;  private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {    this.target = target;    this.interceptor = interceptor;    this.signatureMap = signatureMap;  }  public static Object wrap(Object target, Interceptor interceptor) {    // 从拦截器的注解中获取拦截的类名和方法信息      Map, Set> signatureMap = getSignatureMap(interceptor);    Class type = target.getClass();    // 解析被拦截对象的所有接口(注意是接口)      Class[] interfaces = getAllInterfaces(type, signatureMap);    if (interfaces.length > 0) {      // 生成代理对象,Plugin对象为该代理对象的InvocationHandler      //(InvocationHandler属于java代理的一个重要概念,不熟悉的请参考Java动态代理)       // http://luchunli.blog.51cto.com/2368057/1795921        return Proxy.newProxyInstance(          type.getClassLoader(),          interfaces,          new Plugin(target, interceptor, signatureMap));    }    return target;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {      Set methods = signatureMap.get(method.getDeclaringClass());      if (methods != null && methods.contains(method)) {        return interceptor.intercept(new Invocation(target, method, args));      }      return method.invoke(target, args);    } catch (Exception e) {      throw ExceptionUtil.unwrapThrowable(e);    }  }}

这个Plugin类有三个属性:

private Object target;                  // 被代理的目标类private Interceptor interceptor;             // 对应的拦截器private Map, Set> signatureMap;    // 拦截器拦截的方法缓存


关于InvocationHandler请参阅:Java动态代理


我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对executor执行了多次plugin(org.apache.ibatis.plugin.InterceptorChain)。

  private final List interceptors = new ArrayList();    public Object pluginAll(Object target) {    for (Interceptor interceptor : interceptors) {      target = interceptor.plugin(target);    }    return target;  }

说明:

拦截器在解析MyBatis的配置文件时已经add到了该interceptors拦截器中。

org.apache.ibatis.builder.xml.XMLConfigBuilder类解析MyBatis的配置文件,获取其中配置的Interceptor并调用configuration.addInterceptor(interceptorInstance);配置到拦截器链中。


过程分析:

第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象;

第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...;

这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。


代理链的拦截

代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,它(org.apache.ibatis.plugin.Plugin)的invoke方法是怎么样的呢?

  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {      Set methods = signatureMap.get(method.getDeclaringClass());      if (methods != null && methods.contains(method)) {        // 调用代理类所属拦截器的intercept方法        return interceptor.intercept(new Invocation(target, method, args));      }      return method.invoke(target, args);    } catch (Exception e) {      throw ExceptionUtil.unwrapThrowable(e);    }  }

在invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个Invocation对象,这个对象是什么样子的,他的功能又是什么呢?

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * @author Clinton Begin */public class Invocation {  private Object target;  private Method method;  private Object[] args;  public Invocation(Object target, Method method, Object[] args) {    this.target = target;    this.method = method;    this.args = args;  }  public Object getTarget() {    return target;  }  public Method getMethod() {    return method;  }  public Object[] getArgs() {    return args;  }  // 拦截器最终调用的方法,实际还是通过反射来调用的  public Object proceed() throws InvocationTargetException, IllegalAccessException {    return method.invoke(target, args);  }}

可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。

在每个拦截器的intercept方法内,最后一个语句一定是invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。


在MyBatis中配置的拦截器方式为:

                        

我们假设在MyBatis配置了一个插件,在运行时会发生什么?

1) 所有可能被拦截的处理类都会生成一个代理

2) 处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法

3) 执行插接中的拦截方法后,推进目标的执行

如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。


方法 代理 拦截器 对象 目标 配置 插件 生成 属性 参数 文件 注解 也就是 动态 接口 语句 逻辑 面的 处理 原理 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 南京欣网互联网络科技 华服互联网科技怎么样 云之行互联网科技 互联网科技工作委员会 朗新科技与工业互联网 广州互联网科技公司 安徽鑫晟辉互联网科技 互联网 科技服务平台 老年人脱离互联网科技的作文 承德科技产业互联网发展 现代互联网科技股 上海宁丞科技互联网 湖南一路畅行互联网科技 知域互联网科技有限公司大骗子 搜狐科技互联网 乌鲁木齐新华智原互联网科技学校 上海京穆互联网科技有限公司 互联网的科技体现在哪里 上海旺乐尼互联网科技有限公司 株洲百竞科技的互联网营销顾问 哈尔滨大商圈互联网科技有限公司 中资互联网科技 科技服务与互联网 百单互联网科技有限公司 互联网与医疗科技 深圳育鹏互联网科技有限公司招聘 群盈汇互联网科技有限公司 江苏互联网科技小贷 江苏互联网智能科技产品出厂价 广州全民伙伴互联网科技有限公司
0