目录
[二、MyBatis 责任链的核心拦截点](#二、MyBatis 责任链的核心拦截点)
[三、责任链执行流程(以 StatementHandler 为例)](#三、责任链执行流程(以 StatementHandler 为例))
[1. 整体流程](#1. 整体流程)
[2. 关键环节:嵌套代理构建责任链](#2. 关键环节:嵌套代理构建责任链)
[步骤 1:定义抽象处理者(复用 MyBatis 原生 Interceptor)](#步骤 1:定义抽象处理者(复用 MyBatis 原生 Interceptor))
[步骤 2:实现具体处理者(自定义插件)](#步骤 2:实现具体处理者(自定义插件))
[插件 1:SQL 日志插件(记录最终执行的 SQL)](#插件 1:SQL 日志插件(记录最终执行的 SQL))
[插件 2:分页插件(改写 SQL 拼接分页条件)](#插件 2:分页插件(改写 SQL 拼接分页条件))
[步骤 3:配置插件(构建责任链)](#步骤 3:配置插件(构建责任链))
[步骤 4:执行效果(责任链执行流程)](#步骤 4:执行效果(责任链执行流程))
[五、责任链核心实现:Plugin 类(管理器)](#五、责任链核心实现:Plugin 类(管理器))
[六、MyBatis 责任链的核心特点](#六、MyBatis 责任链的核心特点)
[1. 嵌套代理式责任链(区别于列表式)](#1. 嵌套代理式责任链(区别于列表式))
[2. 不可中断的纯责任链](#2. 不可中断的纯责任链)
[3. 精准拦截(按方法签名匹配)](#3. 精准拦截(按方法签名匹配))
[4. 无侵入扩展](#4. 无侵入扩展)
MyBatis 基于责任链模式 + JDK 动态代理 实现了插件(Interceptor)机制,核心目标是对 SQL 执行全流程(参数处理、SQL 构建、结果集返回等)进行无侵入的拦截增强,多个插件可按顺序形成责任链,依次处理目标对象,最终执行原生逻辑。
与 Spring MVC/Security 的 "列表式责任链" 不同,MyBatis 责任链是嵌套代理式:每个插件通过动态代理包裹目标对象,请求沿代理链传递,本质仍是 "请求沿链传递、处理者自主决策" 的责任链核心思想。
一、核心角色对应(责任链模式)
| 责任链模式角色 | MyBatis 对应实现 | 核心职责 |
|---|---|---|
| 抽象处理者(Handler) | org.apache.ibatis.plugin.Interceptor 接口 |
定义拦截核心方法(intercept)、代理创建方法(plugin),是所有插件的统一接口 |
| 具体处理者 | 自定义插件(如分页插件 PageHelper、SQL 日志插件)、MyBatis 内置插件 | 实现 Interceptor,指定拦截点(如 StatementHandler 的 prepare 方法),处理拦截逻辑后传递请求 |
| 责任链管理器 | org.apache.ibatis.plugin.Plugin 类 |
基于 JDK 动态代理构建插件链,触发拦截方法执行,控制请求传递 |
| 被处理的 "请求" 对象 | MyBatis 核心执行组件(Executor/StatementHandler/ParameterHandler/ResultSetHandler) |
插件拦截的目标,也是请求传递的载体(如修改 StatementHandler 的 SQL 内容) |
| 最终处理者 | MyBatis 原生组件的方法(如 StatementHandler.prepare()) |
责任链执行完毕后,执行原生 SQL 处理逻辑 |
二、MyBatis 责任链的核心拦截点
MyBatis 插件可拦截 4 个核心组件的方法(即责任链可处理的 "请求类型"),覆盖 SQL 执行全流程:
| 拦截组件 | 拦截时机 | 典型用途 |
|---|---|---|
Executor |
SQL 执行前 / 后(query/update) |
分页、缓存增强、SQL 耗时统计 |
StatementHandler |
SQL 构建 / 执行前(prepare/parameterize) |
SQL 改写、动态拼接条件 |
ParameterHandler |
SQL 参数绑定前(setParameters) |
参数加密、参数格式化 |
ResultSetHandler |
结果集处理后(handleResultSets) |
结果集脱敏、数据类型转换 |
三、责任链执行流程(以 StatementHandler 为例)
1. 整体流程
plaintext
MyBatis 执行 SQL →
创建 `StatementHandler` 对象 →
Plugin 类为其生成代理对象(嵌套所有插件)→
调用 `StatementHandler.prepare()` →
触发代理链执行:
插件1.intercept() → 处理逻辑(如改写 SQL)→ 调用 `invocation.proceed()` →
插件2.intercept() → 处理逻辑(如记录 SQL)→ 调用 `invocation.proceed()` →
... →
执行原生 `StatementHandler.prepare()` →
执行 SQL 并返回结果
2. 关键环节:嵌套代理构建责任链
MyBatis 插件链通过 Plugin.wrap() 方法嵌套构建,例如配置了 "分页插件 + 日志插件":
plaintext
原始 StatementHandler →
被日志插件代理(LogPlugin)→
被分页插件代理(PagePlugin)→
最终代理对象
调用代理对象的 prepare() 时,执行顺序为:PagePlugin.intercept() → LogPlugin.intercept() → 原始 prepare(),即配置顺序 = 代理外层顺序 = 拦截执行顺序。
四、代码示例:自定义插件实现责任链
步骤 1:定义抽象处理者(复用 MyBatis 原生 Interceptor)
MyBatis 已提供抽象处理者接口,无需自定义:
java
运行
public interface Interceptor {
// 核心拦截方法:处理逻辑,通过 invocation.proceed() 传递请求
Object intercept(Invocation invocation) throws Throwable;
// 创建代理对象:将当前插件加入责任链
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 设置插件属性(从 mybatis-config.xml 读取)
default void setProperties(Properties properties) {}
}
步骤 2:实现具体处理者(自定义插件)
插件 1:SQL 日志插件(记录最终执行的 SQL)
java
运行
@Intercepts({
// 指定拦截点:拦截 StatementHandler 的 prepare 方法
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class SqlLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取目标对象(StatementHandler)
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 2. 反射获取 SQL 内容(MyBatis 提供的反射工具)
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
String sql = (String) metaObject.getValue("delegate.boundSql.sql");
// 3. 处理逻辑:打印 SQL
System.out.println("【SQL 日志插件】执行 SQL:" + sql);
// 4. 传递请求:执行下一个插件/原生方法
return invocation.proceed();
}
}
插件 2:分页插件(改写 SQL 拼接分页条件)
java
运行
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class PageInterceptor implements Interceptor {
private int pageSize; // 每页条数
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取目标对象
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// 2. 处理逻辑:改写 SQL(拼接 LIMIT)
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
String pageSql = originalSql + " LIMIT " + pageSize;
metaObject.setValue("delegate.boundSql.sql", pageSql);
System.out.println("【分页插件】改写 SQL:" + pageSql);
// 3. 传递请求
return invocation.proceed();
}
@Override
public void setProperties(Properties properties) {
// 读取配置的分页大小
pageSize = Integer.parseInt(properties.getProperty("pageSize", "10"));
}
}
步骤 3:配置插件(构建责任链)
在 mybatis-config.xml 中注册插件,配置顺序即责任链执行顺序:
xml
<configuration>
<plugins>
<!-- 第一个注册:分页插件(外层代理) -->
<plugin interceptor="com.example.PageInterceptor">
<property name="pageSize" value="5"/>
</plugin>
<!-- 第二个注册:日志插件(内层代理) -->
<plugin interceptor="com.example.SqlLogInterceptor"/>
</plugins>
</configuration>
步骤 4:执行效果(责任链执行流程)
当执行 userMapper.selectAll() 时,输出如下:
plaintext
【分页插件】改写 SQL:SELECT * FROM user LIMIT 5
【SQL 日志插件】执行 SQL:SELECT * FROM user LIMIT 5
- 执行顺序:分页插件先拦截(修改 SQL)→ 日志插件后拦截(打印最终 SQL)→ 执行原生
prepare()方法; - 核心传递逻辑:每个插件通过
invocation.proceed()触发下一个插件 / 原生方法执行,实现 "请求沿链传递"。
五、责任链核心实现:Plugin 类(管理器)
MyBatis 的 Plugin 类是责任链的核心,基于 JDK 动态代理实现拦截和传递,核心源码简化如下:
java
运行
public class Plugin implements InvocationHandler {
private final Object target; // 被代理的目标对象(如 StatementHandler)
private final Interceptor interceptor; // 当前插件
private final Map<Class<?>, Set<Method>> signatureMap; // 插件要拦截的方法
// 创建代理对象(嵌套构建责任链)
public static Object wrap(Object target, Interceptor interceptor) {
// 获取插件要拦截的方法映射
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取目标类的所有接口(JDK 动态代理需要接口)
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 生成代理对象:当前 Plugin 作为 InvocationHandler
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap)
);
}
return target; // 无需拦截,返回原对象
}
// 代理对象调用方法时触发(核心:责任链执行)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断当前方法是否是插件要拦截的方法
if (signatureMap.containsKey(method.getDeclaringClass())) {
if (signatureMap.get(method.getDeclaringClass()).contains(method)) {
// 执行插件的 intercept 方法,传入 Invocation(封装目标、方法、参数)
return interceptor.intercept(new Invocation(target, method, args));
}
}
// 不拦截,直接执行目标方法(传递请求)
return method.invoke(target, args);
}
}
关键逻辑:
wrap()方法:为目标对象生成代理,若有多个插件,会嵌套生成代理(如Plugin.wrap(Plugin.wrap(target, pagePlugin), logPlugin));invoke()方法:代理对象调用方法时,先执行插件的intercept方法,再通过Invocation.proceed()触发下一层代理 / 原生方法。
六、MyBatis 责任链的核心特点
1. 嵌套代理式责任链(区别于列表式)
- 传统责任链(如 Spring MVC):用列表存储处理者,循环执行;
- MyBatis 责任链:用动态代理嵌套处理者,请求沿代理链传递,更轻量且适配 "拦截方法执行" 的场景。
2. 不可中断的纯责任链
MyBatis 插件必须通过 invocation.proceed() 传递请求(否则原生逻辑无法执行),无法像 Spring MVC 那样 "中断链",属于纯责任链(请求必须传递到最终处理者)。
3. 精准拦截(按方法签名匹配)
通过 @Intercepts + @Signature 注解指定拦截的 "组件 + 方法 + 参数",仅拦截匹配的方法,避免无效处理。
4. 无侵入扩展
新增插件只需实现 Interceptor 并配置,无需修改 MyBatis 源码,符合 "开闭原则"。
七、典型应用场景
- 分页插件(PageHelper) :拦截
Executor的query方法或StatementHandler的prepare方法,拼接分页 SQL(如LIMIT/ROWNUM); - SQL 监控 / 日志 :拦截
StatementHandler的prepare方法,记录 SQL 内容、执行耗时、参数信息; - 数据脱敏 :拦截
ParameterHandler(入参脱敏,如手机号隐藏中间 4 位)、ResultSetHandler(出参脱敏); - 多租户适配 :拦截
StatementHandler,自动为 SQL 拼接租户 ID 条件(如WHERE tenant_id = ?); - 参数加密 / 解密 :拦截
ParameterHandler对敏感参数(如密码)加密,拦截ResultSetHandler对结果解密。
八、使用注意事项
- 插件顺序:配置顺序决定执行顺序(外层插件先执行),需注意依赖(如分页插件应在日志插件前执行,否则日志打印原 SQL);
- 反射安全 :修改 MyBatis 内部属性(如 SQL)时,必须使用
MetaObject(MyBatis 原生反射工具),避免直接反射导致版本兼容问题; - 性能控制 :插件会增加 SQL 执行耗时,避免过多 / 复杂插件(如同时拦截
Executor和StatementHandler); - 方法匹配 :
@Signature的args必须与目标方法参数完全一致(如StatementHandler.prepare()的参数是Connection和Integer,需精准匹配)。
总结
MyBatis 是责任链模式在 "ORM 框架拦截 SQL 执行" 场景的经典落地:
- 核心载体 :
Interceptor是抽象处理者,Plugin是责任链管理器,通过动态代理嵌套构建插件链; - 执行逻辑 :请求沿代理链传递,每个插件处理后通过
invocation.proceed()传递给下一个插件,最终执行原生逻辑; - 核心价值:无侵入扩展 SQL 执行流程,适配分页、日志、脱敏等多样化业务需求;
- 关键扩展 :自定义插件只需实现
Interceptor,配置后即可加入责任链,是 MyBatis 灵活性的核心体现。
掌握这一机制,是开发 MyBatis 定制化插件(如自定义分页、SQL 改写)的核心前提。