MyBatis 拦截器链执行顺序及原理深度解析:揭开动态代理背后的奥秘

一、什么是 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 所有版本

最佳实践建议

  1. 每个拦截器只处理单一职责
  2. 在 @Intercepts 中明确声明拦截目标
  3. 始终在 intercept() 中调用 proceed()
  4. 对高频操作进行性能优化

通过合理使用拦截器链,开发者可以实现从基础监控到复杂业务逻辑的各种扩展需求,是 MyBatis 高阶开发的必备技能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值