一、原理与设计目标
1.1 拦截点的定位与分类
在 MyBatis 的执行链中,拦截点定义了拦截的边界,常见的拦截点包括 Executor、StatementHandler、ParameterHandler、ResultSetHandler。通过这些点,拦截器能够在不同阶段对 SQL 的执行、参数绑定、以及结果集处理进行观测与改造。理解这些拦截点的定位有助于实现精准而高效的扩展,避免对核心流程造成过度干扰。
从设计角度看,拦截点的可组合性决定了插件的覆盖能力。合理选择拦截点,可以实现日志记录、性能分析、SQL 修改等场景,而不会污染业务代码。
要点总结:拦截点、执行阶段、以及参数传递的清晰定义,是实现稳定拦截的基础。
// 示例:一个简单的拦截器基础骨架
public class SimpleInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 前置逻辑Object result = invocation.proceed();// 后置逻辑return result;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) { }
}
1.2 插件机制的设计要点
MyBatis 的插件机制通过 代理模式把拦截逻辑织入执行链,而不是修改核心实现。这样实现的核心优点是:无侵入性扩展、易于测试、便于维护,同时也提升了代码的可复用性。
另外,插件的配置通常通过 Plugin.wrap 方式注册,配置属性可以在运行时微调拦截行为。通过这种设计,可以在不同应用中复用同一套拦截逻辑。
设计要点回顾:代理封装、可配置性、以及可测试性,共同支撑起高质量的拦截实现。
// 插件注册时的简单演示
<plugins><plugin interceptor="com.example.mybatis.SimpleInterceptor"><property name="log" value="true"/></plugin></plugins>
二、拦截器的工作原理
2.1 MyBatis 拦截器的工作流程
拦截器通过 Interceptor 接口及 @Intercepts、@Signature 注解声明了具体的拦截点。当 MyBatis 构建执行链时,会读取这些信息并在目标对象的方法执行前后触发 intercept 调用,从而实现对 SQL 的拦截与处理。
执行流程的核心是 Invocation,它封装了目标对象、方法及参数。通过 invocation.proceed(),拦截点可以进入下一层执行,或者在前后添加自定义逻辑。
正确使用时,拦截器应该尽量保持 幂等性与低耦合,以避免对原有查询产生副作用。
// 调用流程要点示意(伪代码)
Object intercept(Invocation invocation) {preProcess(invocation);Object result = invocation.proceed(); // 进入下一层执行postProcess(invocation, result);return result;
}
2.2 Interceptor 接口与签名
核心接口 Interceptor 提供了基本的拦截能力,@Intercepts 与 @Signature 注解用于描述拦截的类型、方法及参数。通过这些元数据,MyBatis 可以在运行时将拦截器织入正确的位置。
典型签名包含对 Executor、StatementHandler、ParameterHandler、ResultSetHandler 的方法拦截,例如对 query、update 的拦截。
@Intercepts({@Signature(type = Executor.class, method = "query",args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }),@Signature(type = StatementHandler.class, method = "prepare",args = { Connection.class, Integer.class })
})
三、从实践出发:如何实现一个自定义拦截器
3.1 需求分析与定位
在开始实现前,先明确需求目标,例如 统一日志、收集性能数据、或对某些 SQL 进行动态改写。明确目标有助于选择合适的拦截点,并避免过度拦截导致性能下降。
风险点包括 线程安全、对原有 SQL 的影响范围、以及日志量的可控性。这些要点应在设计阶段就被纳入评估。

// 确定需求:记录执行时间并输出 SQL
public class TimingInterceptor implements Interceptor { /* ... */ }
3.2 实现步骤与关键类
实现一个自定义拦截器通常包含以下步骤:实现 Interceptor 接口、使用 @Intercepts/@Signature 声明拦截点、实现 plugin 方法通过 Plugin.wrap 进行包装、实现 setProperties 以接收外部配置。通过这些步骤可以确保拦截器具备良好的可维护性。
// 完整示例:一个简单日志拦截器
package com.example.mybatis;import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.mapping.MappedStatement;import java.util.Properties;@Intercepts({@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class TimingInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();try {return invocation.proceed();} finally {long took = System.currentTimeMillis() - start;System.out.println("SQL took: " + took + " ms");}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
3.3 注册与测试
将拦截器注册到 MyBatis 配置中是实现生效的关键一步,通常通过 mybatis-config.xml 或 Spring 集成进行注册。测试时应覆盖不同查询场景,确保拦截逻辑在多线程环境下仍然稳定。
<configuration><plugins><plugin interceptor="com.example.mybatis.TimingInterceptor"><property name="log" value="true"/></plugin></plugins>
</configuration>
四、插件开发详解:如何编写和配置 Plugin
4.1 Plugin 的核心概念
插件是对执行链的一个“装饰层”,通过 Plugin.wrap 将拦截器应用到目标对象。插件的核心思想是可插拔的功能模块,可以在不修改数据库访问代码的情况下实现横切关注点。
对性能和稳定性的影响应在设计阶段就评估清楚,避免把太多逻辑堆叠在一个拦截点。
// 插件包装核心逻辑(示意)
public Object plugin(Object target) {return Plugin.wrap(target, this);
}
4.2 插件注入与配置
插件注入通常通过配置文件完成,属性可以在运行时微调行为,例如开启日志、设置阈值等。合理的配置让同一份拦截逻辑能够适用于多种场景。
在实践中,也可以通过环境变量或框架的配置系统注入不同的属性,以便在开发、测试、生产环境之间快速切换。
<plugin interceptor="com.example.mybatis.TimingInterceptor"><property name="enabled" value="true"/><property name="threshold" value="100"/>
</plugin>
五、常见坑与性能考虑
5.1 线程安全与可重复利用
拦截器通常在多线程环境下被共享使用,因此实现应遵循 无状态设计,避免在拦截器实例中存放可变的共享数据。若需要记录状态,应采用 局部变量或线程本地存储,确保线程安全。
敏感操作如反射调用、字符串拼接等应控制在尽可能小的范围内,以减少 锁竞争与 GC 压力。
// 不安全示例:共享字段存储状态
public class UnsafeInterceptor implements Interceptor {private int counter = 0; // 线程不安全@Overridepublic Object intercept(Invocation invocation) { /*...*/ }
}
5.2 对 SQL 日志与调试的影响
日志记录是拦截器常见场景,但过多的日志会显著影响性能,且可能暴露敏感信息。应使用<可控日志级别、必要时开启日志开关,并考虑对日志输出进行分级处理。
在调试阶段,可以增加 可关闭的详细调试信息,上线前切换为简洁的摘要输出,以保持系统的稳定性与可观测性。
// 详细日志示例(可通过配置开关控制)
if (logEnabled) {System.out.println("SQL: " + sql);System.out.println("Parameters: " + Arrays.toString(params));
}


