MyBatis源码深度复盘(六):插件拦截机制源码解析【责任链+PageHelper底层原理】
✨ 专栏系列:MyBatis源码深度复盘全套教程
前面五篇我们吃透了 MyBatis 架构、初始化、动态代理、SQL执行全流程、缓存机制。
本篇我们来搞定 MyBatis 最核心的拓展机制——插件拦截机制。
日常开发中我们常用的 PageHelper分页、数据权限、租户隔离、SQL日志打印、字段加密解密 功能,全部基于 MyBatis 插件实现。
很多同学只会引入插件依赖、配置使用,却完全不懂底层原理,面试经常被问懵:
-
MyBatis插件可以拦截哪些对象?
-
插件多层拦截执行顺序是什么?
-
PageHelper为什么只需一行代码就能实现分页?
-
MyBatis插件底层是动态代理还是拦截器链?
-
如何手写一个自定义MyBatis插件?
本篇逐行源码拆解插件核心原理,吃透责任链拦截机制、代理嵌套逻辑,彻底掌握MyBatis拓展核心!
一、插件机制核心概述
1.1 插件的核心作用
MyBatis 提供插件拦截机制,允许开发者在不修改源码的前提下,对核心执行流程进行增强、改造,实现各类通用拓展功能,符合开闭原则。
1.2 四大可拦截核心对象(面试必背)
MyBatis 仅允许拦截四大核心组件,所有插件功能都是基于这四个对象拓展:
-
Executor(执行器):拦截SQL执行、缓存操作、事务操作(粒度最大)
-
StatementHandler(语句处理器):拦截SQL预处理、SQL执行(最常用,PageHelper基于此实现)
-
ParameterHandler(参数处理器):拦截参数赋值、参数加密解密
-
ResultSetHandler(结果集处理器):拦截结果集封装、数据脱敏、字段转换
重点:无法拦截 Mapper、SqlSession,仅能拦截以上四大底层执行对象
二、插件核心底层原理总览
MyBatis 插件底层依托两大设计模式实现:
-
动态代理模式:为四大核心对象生成代理对象,拦截方法执行
-
责任链模式:多个插件层层嵌套拦截,有序执行
2.1 核心执行流程
项目启动加载插件 → 为目标对象生成多层代理 → 执行方法时逐层拦截 → 执行自定义拓展逻辑 → 执行原生方法
2.2 核心类介绍
-
Interceptor:插件拦截器顶级接口,自定义插件必须实现
-
InterceptorChain:拦截器链,存储所有注册的插件
-
Plugin:代理生成工具类,负责创建代理对象
-
Invocation:方法调用封装对象,保存目标方法、参数、目标对象
三、源码逐行拆解插件加载与代理生成
3.1 插件加载时机
MyBatis 在初始化阶段就会加载所有自定义插件,解析 plugins 配置,存入拦截器链,对应初始化核心方法 pluginElement()。
所有插件在项目启动时完成注册,运行时直接执行,无需重复加载。
3.2 插件代理生成核心源码
四大核心对象创建时,都会调用 Plugin.wrap() 方法,生成代理对象,核心源码如下:
public static Object wrap(Object target, Interceptor interceptor) {
// 获取当前插件需要拦截的对象类型和方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取目标对象需要被拦截的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果有需要拦截的接口,生成JDK动态代理对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 无需拦截,返回原生对象
return target;
}
核心结论:MyBatis 插件底层依然是 JDK动态代理,只拦截指定接口的指定方法。
3.3 Plugin.invoke 拦截核心
代理对象生成后,目标对象任意方法执行都会触发 invoke 方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断当前方法是否需要被拦截
Set<Method> 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);
}
}
这就是插件实现方法拦截的核心入口。
四、Interceptor拦截器接口详解
自定义插件必须实现 Interceptor 接口,包含三个核心方法:
public interface Interceptor {
// 核心:拦截目标方法,执行自定义逻辑
Object intercept(Invocation invocation) throws Throwable;
// 包装目标对象,生成代理
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 读取插件配置参数
default void setProperties(Properties properties) {
// 可读取xml中配置的自定义参数
}
}
4.1 Invocation执行链核心方法
Invocation 封装了目标执行信息,核心方法 proceed():放行当前拦截,执行下一个插件或原生方法
这是责任链可以层层执行的关键!
五、多层插件执行顺序(面试高频)
5.1 插件嵌套原理
如果配置多个插件,MyBatis 会生成多层嵌套代理对象,类似洋葱结构。
5.2 执行顺序规则
-
代理创建顺序:按照配置顺序,从上到下创建代理
-
方法执行顺序:后创建的插件,先执行拦截逻辑
-
放行执行顺序:先创建的插件,最后放行
通俗总结:先配置后执行,后配置先执行
六、PageHelper分页插件底层源码原理
结合本篇插件机制,彻底解密 PageHelper 核心实现,面试高频考点!
6.1 拦截对象
PageHelper 拦截 StatementHandler
6.2 核心实现逻辑
-
通过
PageHelper.startPage()本地线程 ThreadLocal 保存分页参数(页码、条数) -
SQL执行前,拦截 StatementHandler 的 prepare 或 query 方法
-
获取原生待执行SQL,动态拼接 limit 分页语句
-
执行修改后的分页SQL,完成分页查询
-
查询总条数,封装分页结果 PageInfo
-
清空 ThreadLocal 分页参数,防止线程污染
6.3 核心亮点
基于 ThreadLocal 线程隔离 + SQL动态改写,无侵入、无需改业务代码,这就是 PageHelper 极简使用的底层真相。
七、手写自定义MyBatis插件实战(可直接复用)
手把手实现一个SQL执行耗时统计插件,实战掌握插件开发!
7.1 自定义拦截器代码
public interface Interceptor {
// 核心:拦截目标方法,执行自定义逻辑
Object intercept(Invocation invocation) throws Throwable;
// 包装目标对象,生成代理
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 读取插件配置参数
default void setProperties(Properties properties) {
// 可读取xml中配置的自定义参数
}
}
7.2 配置插件
<plugins>
<plugin interceptor="com.xxx.SqlCostInterceptor"/>
</plugins>
启动项目执行SQL,即可自动打印每条SQL的执行耗时,插件开发就是这么简单!
八、面试高频真题解答
Q1:MyBatis插件可以拦截哪些对象?
MyBatis插件仅支持拦截四大核心对象:Executor执行器、StatementHandler语句处理器、ParameterHandler参数处理器、ResultSetHandler结果集处理器,可分别实现SQL执行拦截、SQL改写、参数处理、结果封装拓展功能。
Q2:MyBatis插件底层原理?
基于JDK动态代理+责任链模式实现,项目启动加载所有插件,为四大核心目标对象生成嵌套代理对象;方法执行时被插件拦截,执行自定义拓展逻辑,通过invocation.proceed()放行,实现多层插件有序执行。
Q3:多个插件执行顺序是什么?
插件按照配置顺序从上到下加载创建代理,执行时遵循后加载先执行、先加载后执行的规则,形成洋葱式嵌套执行结构。
Q4:PageHelper分页原理?
PageHelper自定义插件拦截StatementHandler,通过ThreadLocal存储分页参数,SQL执行前动态拼接limit语句改写原生SQL,执行分页查询并统计总条数,最终封装分页结果,同时清空线程参数避免污染。
九、本篇核心总结
1. MyBatis插件只能拦截四大核心底层对象,不支持拦截Mapper、SqlSession。
2. 底层核心:JDK动态代理 + 责任链模式,实现无侵入拓展。
3. 多层插件为嵌套代理,执行顺序遵循后配置先执行。
4. PageHelper核心是拦截StatementHandler + 动态改写SQL + ThreadLocal线程隔离。
5. 自定义插件实现简单,可快速实现SQL监控、数据脱敏、租户隔离等通用功能。
下篇预告
下一篇我们深度拆解 MyBatis事务机制与批量操作源码:
✅ MyBatis事务提交、回滚底层逻辑
✅ BATCH批量执行器性能优化原理
✅ 批量操作常见坑点与解决方案
持续更新MyBatis源码全套教程,点赞收藏不迷路!
更多推荐

所有评论(0)