MyBatis 源码分析 - 插件机制
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址、Mybatis-Spring 源码分析 GitHub 地址、Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
该系列其他文档请查看:《精尽 MyBatis 源码分析 - 文章导读》
插件机制
开源框架一般都会提供插件或其他形式的扩展点,供开发者自行扩展,增加框架的灵活性
当然,MyBatis 也提供了插件机制,基于它开发者可以进行扩展,对 MyBatis 的功能进行增强,例如实现分页、SQL分析、监控等功能,本文会对 MyBatis 插件机制的原理以及如何实现一个自定义的插件来进行讲述
我们在编写插件时,除了需要让插件类实现 org.apache.ibatis.plugin.Interceptor
接口,还需要通过注解标注该插件的拦截点,也就是插件需要增强的方法,MyBatis 只提供下面这些类中定义的方法能够被增强:
-
Executor:执行器
-
ParameterHandler:参数处理器
-
ResultSetHandler:结果集处理器
-
StatementHandler:Statement 处理器
植入插件逻辑
在《MyBatis的SQL执行过程》一系列文档中,有讲到在创建Executor、ParameterHandler、ResultSetHandler和StatementHandler对象时,会调用InterceptorChain
的pluginAll
方法,遍历所有的插件,调用Interceptor
插件的plugin
方法植入相应的插件逻辑,所以在 MyBatis 中只有上面的四个对象中的方法可以被增强
代码如下:
分页插件示例
我们先来看一个简单的插件示例,代码如下:
在上面的分页插件中,@Intercepts
和@Signature
两个注解指定了增强的方法是Executor.query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
,也就是我们使用到的 Executor 执行数据库查询操作的方法
在实现的 intercept
方法中,通过 RowBounds
参数获取分页信息,并生成相应的 SQL(拼接了 limit) ,并使用该 SQL 作为参数重新创建一个 StaticSqlSource
对象,最后通过反射替换 MappedStatement
对象中的 sqlSource
字段,这样就实现了一个简单的分页插件
上面只是一个简单的示例,实际场景中慎用
Interceptor
org.apache.ibatis.plugin.Interceptor
:拦截器接口,代码如下:
- intercept方法:拦截方法,插件的增强逻辑
- plugin方法:应用插件,往目标对象中植入相应的插件逻辑,如果应用成功则返回一个代理对象(JDK动态代理),否则返回原始对象,默认调用
Plugin
的wrap
方法 - setProperties方法:设置拦截器属性
Invocation
org.apache.ibatis.plugin.Invocation
:被拦截的对象信息,代码如下:
Plugin
org.apache.ibatis.plugin.Plugin
:实现InvocationHandler接口,用于对拦截的对象进行,一方面提供创建动态代理对象的方法,另一方面实现对指定类的指定方法的拦截处理,MyBatis插件机制的核心类
构造方法
wrap方法
wrap(Object target, Interceptor interceptor)
方法,创建目标类的代理对象,方法如下:
- 调用
getSignatureMap
方法,获得拦截器中需要拦截的类的方法集合,有就是通过@Intercepts
和@Signature
两个注解指定的增强的方法 - 获得目标对象的 Class 对象(父类或者接口)
- 获得目标对象所有需要被拦截的 Class 对象
- 如果需要被拦截,则为目标对象的创建一个动态代理对象(JDK 动态代理),代理类为
Plugin
对象,并返回该动态代理对象 - 否则返回原始的目标对象
getSignatureMap方法
getSignatureMap(Interceptor interceptor)
方法,获取插件需要增强的方法,方法如下:
- 通过该插件上面的
@Intercepts
和@Signature
注解,获取到所有需要被拦截的对象中的需要增强的方法
getAllInterfaces方法
getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap)
方法,判断目标对象是否需要被插件应用,方法如下:
- 入参
signatureMap
就是getSignatureMap
方法返回的该插件需要增强的方法 - 返回存在于
signatureMap
集合中所有目标对象的父类或者接口
invoke方法
invoke(Object proxy, Method method, Object[] args)
方法,动态代理对象的拦截方法,方法如下:
- 获得目标方法所在的类需要被拦截的方法
- 如果被拦截的方法包含当前方法,则将当前方法封装成
Invocation
对象,调用Interceptor
插件的intercept
方法,执行插件逻辑 - 否则执行原有方法
这样一来,当你调用了目标对象的对应方法时,则会进入该插件的intercept
方法,执行插件逻辑,扩展功能
InterceptorChain
org.apache.ibatis.plugin.InterceptorChain
:拦截器链,用于将所有的拦截器按顺序将插件逻辑植入目标对象,代码如下:
配置MyBatis插件都会保存在interceptors
集合中,可以回顾到《初始化(一)之加载mybatis-config.xml》的XMLConfigBuilder小节的pluginElement
方法,会将解析到的依次全部添加到Configuration
的InterceptorChain
对象中,代码如下:
总结
本文分析了 MyBatis 中插件机制,总体来说比较简单的,想要实现一个插件,需要实现 Interceptor
接口,并通过@Intercepts
和@Signature
两个注解指定该插件的拦截点(支持对Executor、ParameterHandler、ResultSetHandler 和 StatementHandler 四个对象中的方法进行增强),在实现的intercept
方法中进行逻辑处理
在 MyBatis 初始化的时候,会扫描插件,将其添加到InterceptorChain
中
然后 MyBatis 在 SQL 执行过程中,创建上面四个对象的时候,会将创建的对象交由InterceptorChain
去处理,遍历所有的插件,通过插件的plugin
方法为其创建一个动态代理对象并返回,代理类是Plugin
对象
在Plugin
对象中的invoke
方法中,将请求交由插件的intercept
方法去处理
虽然 MyBatis 的插件机制比较简单,但是想要实现一个完善且高效的插件却比较复杂,可以参考PageHelper分页插件
到这里,相信大家对 MyBatis 的插件机制有了一定的了解,感谢大家的阅读!!!😄😄😄
参考文章:芋道源码《精尽 MyBatis 源码分析》
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/14015228.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?