一、什么是 MyBatis 拦截器链?
MyBatis 拦截器链(Interceptor Chain)是 MyBatis 提供的一种扩展机制,允许开发者在 SQL 执行的关键环节(如执行查询、更新操作)插入自定义逻辑。它类似于给 SQL 执行流程安装的“可编程插件”,能够在不修改框架源码的情况下,实现日志记录、数据加密、分页处理等高级功能。
举个例子:
假设你想监控所有 SQL 的执行时间,传统方式需要在每个 Mapper 方法中添加计时逻辑,而通过拦截器链,只需编写一个拦截器,即可全局生效。
二、拦截器链的生成机制
1. 核心原理:动态代理的嵌套包装
MyBatis 拦截器链基于动态代理实现,通过“层层包装”的方式形成链式结构。其核心过程如下:
java
// 原始执行器(如 SimpleExecutor)
Executor rawExecutor = new SimpleExecutor();
// 第一次包装:分页拦截器
Executor pageProxy = (Executor) PageInterceptor.plugin(rawExecutor);
// 第二次包装:日志拦截器
Executor finalProxy = (Executor) LogInterceptor.plugin(pageProxy);
最终形成的代理链结构:
[LogProxy] ← 外层代理(后配置的拦截器)
└─ [PageProxy] ← 中间层代理(先配置的拦截器)
└─ SimpleExecutor ← 原始执行器
2. 关键代码解析
java
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
// 核心方法:按顺序包装目标对象
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target); // 每次生成新的代理
}
return target;
}
}
- 执行顺序:拦截器的配置顺序决定了代理的包装顺序,但实际执行时顺序相反(外层代理先执行)。
三、拦截器链的执行流程
1. 责任链模式的核心流程
当调用 executor.update() 方法时,触发链式执行:
1. 日志拦截器的前置处理(记录开始时间)
2. 分页拦截器的前置处理(修改 SQL)
3. 原始 SQL 执行
4. 分页拦截器的后置处理(处理结果集)
5. 日志拦截器的后置处理(计算耗时)
2. 为什么必须调用 invocation.proceed()?
proceed() 方法是责任链传递的“接力棒”,其作用包括:
- 触发后续拦截器:若不调用,后续所有拦截器和原始方法均不执行
- 维护调用栈完整性:确保代理链正确展开和回收
java
public class Plugin implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) {
if (需要拦截) {
// 执行拦截逻辑
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
}
}
public class Invocation {
public Object proceed() throws Throwable {
// 触发下一个拦截器或原始方法
return method.invoke(target, args);
}
}
四、如何添加拦截器?
1. XML 配置方式(推荐)
xml
<configuration>
<plugins>
<!-- 先配置的拦截器在内层 -->
<plugin interceptor="com.example.PageInterceptor">
<property name="maxLimit" value="1000"/>
</plugin>
<!-- 后配置的拦截器在外层 -->
<plugin interceptor="com.example.LogInterceptor"/>
</plugins>
</configuration>
2. 编程式添加
java
SqlSessionFactory sqlSessionFactory = ...;
Configuration config = sqlSessionFactory.getConfiguration();
// 创建拦截器实例
Interceptor customInterceptor = new CustomInterceptor();
config.addInterceptor(customInterceptor);
五、常见问题与解决方案
1. 拦截器未生效的常见原因
| 问题原因 | 解决方案 |
|---|---|
| 配置顺序错误 | 将基础拦截器(如日志)放在配置的末尾 |
未正确实现 plugin() | 使用默认实现:Plugin.wrap(target, this) |
| 方法签名不匹配 | 检查 @Signature 注解参数是否正确 |
2. 性能优化技巧
- 精确拦截范围:通过
@Intercepts指定具体拦截的方法 - 缓存反射对象:避免重复获取 Method 对象
- 减少代理层级:非必要情况下不添加过多拦截器
六、实际应用场景
1. SQL 执行时间统计
java
public Object intercept(Invocation invocation) throws Throwable {
long start = System.nanoTime();
try {
return invocation.proceed();
} finally {
long cost = (System.nanoTime() - start) / 1000;
System.out.println("SQL 执行耗时:" + cost + "μs");
}
}
2. 自动分页实现
java
public Object intercept(Invocation invocation) {
// 1. 解析分页参数
// 2. 执行 COUNT 查询获取总数
// 3. 修改原始 SQL 添加 LIMIT 子句
// 4. 返回包装后的 Page 对象
}
3. 数据敏感字段加密
java
public Object intercept(Invocation invocation) {
// 查询时解密:处理结果集
// 更新时加密:处理参数对象
}
七、总结
MyBatis 拦截器链通过动态代理和责任链模式的巧妙结合,实现了高度灵活的功能扩展。其核心优势包括:
| 优势 | 说明 |
|---|---|
| 非侵入式扩展 | 无需修改框架源码 |
| 灵活的执行顺序控制 | 通过配置顺序控制代理层级 |
| 细粒度的拦截范围 | 精确到具体接口和方法 |
| 良好的兼容性 | 支持 XML 和编程式配置,兼容 MyBatis 所有版本 |
最佳实践建议:
- 每个拦截器只处理单一职责
- 在
@Intercepts中明确声明拦截目标 - 始终在
intercept()中调用proceed() - 对高频操作进行性能优化
通过合理使用拦截器链,开发者可以实现从基础监控到复杂业务逻辑的各种扩展需求,是 MyBatis 高阶开发的必备技能
296

被折叠的 条评论
为什么被折叠?



