爱党爱国 發表於 2026-1-8 15:37:31

MyBatis插件机制的使用及说明

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>拦截器介绍及配置</li><li>源码分析</li><li>Plugin.wrap方法</li><li>总结</li></ul></div><p>MyBatis插件机制是该框架提供的一种灵活扩展方式,允许开发者在不修改框架源代码的情况下对MyBatis的功能进行定制和增强。</p>
<p>这种机制主要通过拦截器(Interceptor)实现,使得开发者可以拦截和修改MyBatis在执行SQL语句过程中的行为。</p>
<p style="text-align:center"><br /><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010814440452.png" /></p>
<p><strong>MyBatis允许使用插件来拦截的方法调用包括:</strong></p>
<ul><li>Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的方法</li><li>ParameterHandler (getParameterObject, setParameters) 拦截参数的处理</li><li>ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理</li><li>StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理</li></ul>
<p class="maodian"></p><h2>拦截器介绍及配置</h2>
<p>MyBatis拦截器的接口定义,plugin方法用于某些处理器(Handler)的构建过程。interceptor方法用于处理代理类的执行。</p>
<p>setProperties方法用于拦截器属性的设置:</p>
<div class="jb51code"><pre class="brush:java;">public interface Interceptor {

Object intercept(Invocation invocation) throws Throwable;

Object plugin(Object target);

void setProperties(Properties properties);

}
</pre></div>
<p>MyBatis默认没有一个拦截器接口的实现类。</p>
<p>下面的MyBatis官网的一个拦截器实例,这个拦截器拦截Executor接口的update方法,所有执行executor的update方法都会被该拦截器拦截到:</p>
<div class="jb51code"><pre class="brush:java;">@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
}
public Object plugin(Object target) {
    return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
</pre></div>
<div class="jb51code"><pre class="brush:xml;">&lt;plugins&gt;
    &lt;plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"&gt;&lt;/plugin&gt;
&lt;/plugins&gt;
</pre></div>
<p class="maodian"></p><h2>源码分析</h2>
<p>先从源头-&gt;配置文件开始分析:</p>
<p>XMLConfigBuilder解析MyBatis全局配置文件的pluginElement私有方法:</p>
<div class="jb51code"><pre class="brush:java;">private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
      }
    }
}
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}

</pre></div>
<p>InterceptorChain类包含一个Interceptor类型的列表,用于存储拦截器。</p>
<p>类中有三个方法:pluginAll()用于将所有拦截器应用于目标对象:</p>
<div class="jb51code"><pre class="brush:java;">public class InterceptorChain {

    private final List&lt;Interceptor&gt; interceptors = new ArrayList&lt;Interceptor&gt;();

    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&lt;Interceptor&gt; getInterceptors() {
      return Collections.unmodifiableList(interceptors);
    }

}
</pre></div>
<p>一开始说的几种方法都会调用pluginAll:</p>
<div class="jb51code"><pre class="brush:java;">public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   ...
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010814440448.jpg" /></p>
<p>由于可以拦截StatementHandler,这个接口主要处理sql语法的构建,因此比如分页的功能,可以用拦截器实现,只需要在拦截器的plugin方法中处理StatementHandler接口实现类中的sql即可,可使用反射实现。</p>
<p class="maodian"></p><h2>Plugin.wrap方法</h2>
<p>Mybatis 中的 Plugin.wrap方法用于生成代理对象,实现对目标对象的增强。在 Mybatis 中,插件是通过动态代理实现的,而 Plugin.wrap方法就是用于创建代理对象的核心方法。</p>
<p>具体来说,Plugin.wrap方法接收两个参数:</p>
<p><strong>一个是目标对象(target),另一个是插件实例(plugin)。</strong></p>
<p>方法的主要作用是将插件实例和目标对象进行绑定,然后通过动态代理技术生成一个新的代理对象,该代理对象在调用目标对象的方法时,会先执行插件实例中对应的拦截方法(interceptor),从而实现对目标对象的功能增强。</p>
<div class="jb51code"><pre class="brush:java;">public class Plugin implements InvocationHandler {
private Object target;// 被代理的目标类

private Interceptor interceptor;// 对应的拦截器

private Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt; signatureMap;// 拦截器拦截的方法缓存

public static Object wrap(Object target, Interceptor interceptor) {
    Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt; signatureMap = getSignatureMap(interceptor);
    Class&lt;?&gt; type = target.getClass();
    Class&lt;?&gt;[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length &gt; 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
}
}
</pre></div>
<p class="maodian"></p><h2>总结</h2>
<p>如果开发者对MyBatis的内部工作原理理解不深,使用拦截器可能会意外改变底层行为,导致难以预料的问题。</p>
<p>当使用多个拦截器时,需要仔细管理它们的执行顺序,以确保它们按预期顺序应用,这增加了管理复杂性。同时在编写插件时需注意以下几个原则:</p>
<ul><li>不编写不必要的插件</li><li>实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否则直接返回,这样可以减少目标被代理的次数。</li></ul>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>深入探究MyBatis插件机制灵活扩展及自定义增强框架能力</li><li>MyBatis插件机制超详细讲解</li><li>MyBatis-Plus插件机制及通用Service新功能</li><li>浅谈myBatis中的插件机制</li><li>MyBatis Plus插件机制与执行流程原理分析详解</li><li>mybatis的插件机制示例详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: MyBatis插件机制的使用及说明