Mybatis框架源码笔记(八)之Plugin插件原理解析
创始人
2025-05-28 12:56:50

1、插件概述

引用一段官网的译文

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。
如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏
MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

2、自定义插件实现示例

2.1 自定义插件实现步骤

自定义插件的实现步骤大致如下:

  • 实现Mybatis框架的Interceptor接口

  • 全局配置文件mybatis-config.xml中配置自定义插件即可

下面通过一个示例代码来演示一下具体流程

2.2 自定义插件示例代码

我这里演示一下,自定义SQl语句的拦截方法, 在SQL语句的执行完毕之后, 修改返回的集合中每一个输出对象的某一个具体属性(这里只是演示怎么用, 演示代码场景无法通用,不能用于生产请知,如果有兴趣可以自行研究拓展 )

自定义拦截器MyInterceptor
在这里插入图片描述

package com.kkarma.plugins;import com.kkarma.pojo.LibBook;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.util.List;
import java.util.Properties;/*** @author kkarma* @date 2023/1/17*/
@Intercepts({@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class MyInterceptor implements Interceptor {private String author;/*** 执行拦截逻辑的方法* @param invocation* @return* @throws Throwable* @author kkarma* @date 2023-01-17*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("method will be invoked......");List list = (List)invocation.proceed();list.stream().forEach(v -> v.setBookName(v.getBookName() + "----mod"));System.out.println("method has been invoked......");return list;}/*** 是否触发intercept方法* @param target* @return* @throws Throwable* @author kkarma* @date 2023-01-17*/@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}/*** 自定义插件属性参数配置* @param properties* @return* @throws Throwable* @author kkarma* @date 2023-01-17*/@Overridepublic void setProperties(Properties properties) {Object author = properties.get("author");System.out.println(author);}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}
}

修改全局配置文件mybatis-config.xml

这里注意Mybatis-config.xml文件中各个标签元素的声明顺序
在这里插入图片描述

在全局配置文件中引入自定义插件
在这里插入图片描述

    

以上就是自定义插件的所有实现步骤, 是不是很简单, 下面我们测试一下:

2.3 自定义插件示例代码测试

我们写一个单元测试,查询数据库某个单表中的所有数据,看看我们的自定义插件是否生效

@Test
public void testInterceptor() {SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {SqlSessionFactory factory = factoryBuilder.build(ins);SqlSession sqlSession = factory.openSession(true);SqlSession sqlSession1 = factory.openSession(true);LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);List libBooks = mapper.selectAllBook();libBooks.forEach(System.out::println);System.out.println("------------------------------------------");sqlSession.close();} catch (IOException e) {e.printStackTrace();}
}

在这里插入图片描述
在这里插入图片描述
说明我们的自定义插件生效了, 从这里可以看出自定义插件的步骤还是比较简单的,接下来我们通过Mybatis的源码,分析下插件的实现原理究竟是怎么回事。

3 插件实现原理剖析

3.1 插件是如何解析引入的

上面的实例示例代码中我们在Mybatis-config.xml中声明了我们自定义的Interceptor,那么在全局配置文件的解析类中必然存在专门的方发负责解析处理我们自定义插件模块的方法,
在这里插入图片描述
XMLConfigBuilder类的parseConfiguration()方法中调用pluginEelment
在这里插入图片描述

pluginEelment方法用来解析全局配置文件中的plugins标签,然后对应的创建Interceptor对象,并且封装对应的属性信息。最后调用了Configuration对象中addInterceptor(interceptorInstance)完成拦截器注册在这里插入图片描述
configuration.addInterceptor(interceptorInstance)方法如下:
在这里插入图片描述
在这里插入图片描述
Mybatis中的InterceptorChain
在这里插入图片描述

3.2 插件的作用对象是谁?

开篇我们就已经进行了说明:

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

之前我们在说ExecutorStatementHandler ParameterHandlerResultSetHandler 对象创建的过程时简单提过关于拦截器拦截这些对象来实现功能扩展, 下面来看看,上面四个核心类在创建失败都会调用
Configuration类中的如下代码:

  • Executor 对象创建
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : 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;}

在我们调用Executor.query()方法时,我们的拦截器就开始工作了
在这里插入图片描述
在这里插入图片描述

  • StatementHandler 对象创建
  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;}
  • ParameterHandler 对象创建
  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;}
  • ResultSetHandler 对象创建
  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;}

3.1 插件拦截的处理过程

3.1.1 单个插件的拦截处理过程

整个过程就是使用责任链模式结合

拦截器链中保存了多个拦截器,会遍历所有的拦截器,调用Interceptor.plugin(Object target)方法

Interceptor.plugin(Object target)方法会调用Plugin类的wrap()方法返回拦截目标对象的动态代理对象

Plugin类实现了InvocationHandler接口, 所以最后代理对象的invoke()方法会被调用

Plugin类的invoke()方法中调用了自定义拦截器的intercept(Invocation invocation)方法,这里就会在原来需要执行的方法之前和之后调用我们的拦截处理逻辑对方法的输入和输出进行扩展处理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1.2 多个插件的拦截处理流程

如果我们有多个自定义的拦截器,那么他的拦截执行流程是怎么样的呢?

这里我增加了一个拦截器插件, 拦截的还是Executor.classquery方法, 通过具体的代码示例来验证一下我们的猜想。

package com.kkarma.plugins;import com.kkarma.pojo.LibBook;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.util.List;
import java.util.Properties;/*** @author kkarma* @date 2023/1/17*/
@Intercepts({@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class MyLogInterceptor implements Interceptor {private String logger;/*** 执行拦截逻辑的方法* @param invocation* @return* @throws Throwable* @author kkarma* @date 2023-01-17*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("拦截方法执行之前记录日志...");List list = (List)invocation.proceed();System.out.println("拦截方法执行之后再次记录日志...");return list;}/*** 是否触发intercept方法* @param target* @return* @throws Throwable* @author kkarma* @date 2023-01-17*/@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}/*** 自定义插件属性参数配置* @param properties* @return* @throws Throwable* @author kkarma* @date 2023-01-17*/@Overridepublic void setProperties(Properties properties) {Object logger = properties.get("logger");System.out.println(logger);}public String getLogger() {return logger;}public void setLogger(String logger) {this.logger = logger;}
}

mybatis-config.xml中注册插件顺序如下:

    

执行调用后控制台打印结果如下图:
在这里插入图片描述

3.1.2.1 拦截器注册的过程

首先我们在配置文件中注册的顺序是MyInterceptor -> MyLogInterceptor
在这里插入图片描述

3.1.2.2 代理对象的创建过程

从配置文件中解析创建的Interceptors对象肯定是按照定义的顺序解析出来的, 所以这里再进行动态代理创建的时候也是按照注册的顺序去创建的 MyInterceptor -> MyLogInterceptor
在这里插入图片描述

3.1.2.3 动态代理对象的调用过程

在这里插入图片描述
继续执行,发现MyInteceptor的intercept()方法被执行了,然后query()方法被执行,在依次从内往外返回。
在这里插入图片描述
从打印日志我们可以看出执行调用的顺序注册和创建动态代理对象的顺序刚好相反。
在这里插入图片描述

3.1.2.4 结论

  • 将多个插件注册到InterceptorChain的List是按照插件在配置文件中定义的顺序从上往下的顺序解析、添加的。
  • 创建代理对象的时候也是按照InterceptorChain的List的顺序代理
  • 调用执行的过程和注册创建的过程刚好相反

注册创建的过程(包装礼物的过程):

就是我们现在有一堆好看的太空沙(目标对象)想送给别的小朋友, 不能直接送给人吧,

于是你找来了一个瓶子(MyInteceptor)把太空沙装到里面,

但是这个瓶子也很难看, 于是你又骂了一个精致的礼物盒(MyLogInteceptor)做了一下包装,现在好看了,你把它送给你的朋友了。

调用执行的过程(拆礼物的过程):

你的朋友收到了礼物, 她会先把礼物盒拆开(MyLogInteceptor.intercept())被调用,会发现太空沙被用瓶子转起来了,

于是他又把瓶子(MyInteceptor.intercept())打开了,把太空沙(目标对象)倒出来了, 现在她可以开心的玩耍了。

在这里插入图片描述
StatementHandler、ParameterHandler、ResultSetHandler的拦截器与Executor的处理流程相同, 这里就不多加赘述了, 感兴趣可以自行研究。

相关内容

热门资讯

2021蓝桥杯真题公约数(填空... 题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果...
智能马桶杀菌以及光传感方案 智能马桶杀菌模组,安装在马桶改版底部,实现座垫区域消毒、池内消毒、臀洗喷...
腿玩年是什么意思 ,腿玩年是什... 腿玩年是什么意思 目录腿玩年是什么意思 腿玩年是什么意思玩年腿什么意思啊?腿可玩年是什么意思腿玩年是...
校园yy小说,求校园YY小说 ... 校园yy小说目录校园yy小说求校园YY小说有什么好看的校园YY小说校园yy小说 求校园YY小说校园狂...
骏派D60和宝骏510哪个好?... 今天给各位分享骏派D60和宝骏510哪个好?的知识,其中也会对骏派和宝骏哪个质量好进行解释,如果能碰...
黄河两大支流(黄河两大支流位置... 今天给各位分享黄河两大支流的知识,其中也会对黄河两大支流位置进行解释,如果能碰巧解决你现在面临的问题...
Android Listvie... 上一篇文章中我们讲了Android Listview SimpleAdapter的使用完整示例&#x...
linux下实现RS485驱动... 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮...
GoLang string与s... 这篇文章主要介绍了GoLang string与strings.Builder使用对比,...
爱的创可贴大结局 ,爱的创可贴... 爱的创可贴大结局 目录爱的创可贴大结局 爱的创可贴第几集在一起爱的创可贴结局是什么?电视剧爱的创可贴...
爱在春天结局是什么 ,爱在春天... 爱在春天结局是什么 目录爱在春天结局是什么 爱在春天结局是什么爱在春天大结局爱在春天大结局爱在春天结...
数据挖掘(2.3)--数据预处... 目录 三、数据集成和转换 1.数据集成  2.数据冗余性  2.1 皮尔森相关系数 2.2卡方检验 ...
怎么做水印 ,身份证水印怎么做... 怎么做水印 目录怎么做水印 身份证水印怎么做?ps怎么做水印怎么做水印 2. 设计水印:在设计水印时...
夫妻那些事的演员 ,夫妻那些事... 夫妻那些事的演员 目录夫妻那些事的演员 夫妻那些事主演是谁夫妻那些事演员表夫妻那些事演员表介绍夫妻那...
钉钉,下沉进农田 在这个古老的产业里,数字化没有被放到更高的位置,但难点依旧存在。钉钉恰是...
2023年全国最新二级建造师精... 百分百题库提供二级建造师考试试题、二建考试预测题、二级建造师考试真题、二建证考试题库等,...
点金胜手结局 ,点金胜手结局 ... 点金胜手结局 目录点金胜手结局 点金胜手结局点金胜手30黄宗泽最后跟谁在一起点金胜手大结局点金胜手结...
天苍苍野茫茫是什么歌 ,天苍苍... 天苍苍野茫茫是什么歌 目录天苍苍,野茫茫这句歌词的歌名是什么?天苍苍野茫茫风吹草低见牛羊是哪里的民歌...
未来日记漫画结局,《未来日记》... 未来日记漫画结局目录未来日记漫画结局《未来日记》结局是什么?漫画《未来日记》最后的结局是什么样的?未...
投名状讲的是什么 ,投名状说的... 投名状讲的是什么 目录投名状讲的是什么 投名状说的是什么事情投名状这个电影到底表达了个什么意思投名状...
《Spring Boot 趣味... 牛刀小试——五分钟入门 Spring Boot 万物皆可 Hello World 创建一个 Web ...
硬核~ 阿里人都在内卷的Spr... 前言 这份SpringBoot实战文档,结合典型业务场景,全面介绍基于S...
新娘印度电视剧大结局 ,印度电... 新娘印度电视剧大结局 目录新娘印度电视剧大结局 印度电视剧新娘的最后结局是什么?大结局的印度剧《新娘...
当婆婆遇上妈结局 ,《婆婆遇上... 当婆婆遇上妈结局 目录当婆婆遇上妈结局 《婆婆遇上的妈》电视剧结局是什么?《当婆婆遇上妈》的结局是啥...
洪水来临时正确的做法是什么,发... 洪水来临时正确的做法是什么目录洪水来临时正确的做法是什么发生洪水时的正确做法是什么遇到洪水的正确做法...
baci是什么意思 ,baci... baci是什么意思 目录baci是什么意思 baci是什么意思面基是什么意思啊?baci是什么意思 ...
经典卷积模型回顾26—基于知识... ResNet-152 是由微软亚洲研究院 (Microsoft Research Asia) 发布的...
HTTPS 之fiddler抓... Jmeter接口测试和接口自动化测试从入门到精通,全套项目实战!...
Vmware Ubuntu虚拟... 一、背景 先来说一下我的需求背景,我是在VMware中安装的Ubuntu虚拟机...
友谊的小船说翻就翻是什么意思 ... 友谊的小船说翻就翻是什么意思 目录友谊的小船说翻就翻是什么意思 网络语友谊的小船说翻就翻出自哪里,是...