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;"><plugins>
<plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
</plugins>
</pre></div>
<p class="maodian"></p><h2>源码分析</h2>
<p>先从源头->配置文件开始分析:</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<Interceptor> interceptors = new ArrayList<Interceptor>();
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<Interceptor> 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<Class<?>, Set<Method>> signatureMap;// 拦截器拦截的方法缓存
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 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]