
1. MyBatis拦截器机制概述MyBatis拦截器Interceptor是其框架中极具特色的扩展机制它基于动态代理模式实现允许开发者在SQL执行生命周期的关键节点插入自定义逻辑。这种设计类似于电路系统中的保险丝——在电流通过的关键路径上设置可控制的干预点。拦截器的核心接口Interceptor仅包含三个方法public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }其中intercept方法是最核心的拦截逻辑实现点而plugin方法通过Plugin.wrap()实现动态代理的封装。这种设计使得MyBatis四大核心组件Executor、StatementHandler、ParameterHandler、ResultSetHandler的执行过程变得可观测、可干预。关键理解拦截器本质上是通过JDK动态代理或CGLIB对目标对象生成代理对象在代理对象的方法调用前后插入拦截逻辑。这与Spring AOP的环绕通知Around Advice有异曲同工之妙。2. MyBatis启动流程全景解析2.1 配置加载阶段当SqlSessionFactoryBuilder.build()方法被调用时MyBatis启动流程正式开启。这个阶段会完成以下关键操作XML配置解析通过XMLConfigBuilder解析mybatis-config.xml文件形成Configuration对象拦截器注册在解析plugins标签时通过InterceptorChain收集所有配置的拦截器类型处理器注册处理typeHandlers标签内容映射器加载解析mappers配置加载SQL映射文件这个过程中最值得注意的是拦截器的实例化顺序。MyBatis会严格按照配置文件中的声明顺序创建拦截器实例但实际执行时的拦截顺序是相反的后配置的先执行。2.2 组件代理化阶段在Configuration对象初始化完成后MyBatis会通过以下方法对核心组件进行代理包装public ParameterHandler newParameterHandler(...) { ParameterHandler handler new DefaultParameterHandler(...); handler (ParameterHandler) interceptorChain.pluginAll(handler); return handler; }interceptorChain.pluginAll()方法会遍历所有拦截器对目标对象进行层层包装。假设我们配置了三个拦截器InterceptorA、InterceptorB、InterceptorC那么最终的代理对象结构如下所示代理对象结构 InterceptorC - InterceptorB - InterceptorA - 原始目标对象这种嵌套代理的结构意味着拦截器的执行顺序与配置顺序相反后配置的拦截器会先被执行。3. 拦截器与SQL执行流程的交互3.1 执行器(Executor)拦截点Executor是MyBatis最顶层的执行控制者其拦截时机包括update()增删改操作执行前query()查询操作执行前后commit()/rollback()事务提交/回滚时典型的执行时序如下Executor.query()被调用拦截器链开始执行前置处理创建StatementHandler实例执行SQL并获取结果拦截器链执行后置处理返回结果3.2 语句处理器(StatementHandler)拦截点StatementHandler负责SQL语句的预处理和参数设置关键拦截方法包括prepare()创建Statement对象时parameterize()设置参数时query()/update()执行SQL时一个常见的分页拦截器就是在prepare()阶段修改原始SQL添加LIMIT子句。3.3 参数处理器(ParameterHandler)拦截点ParameterHandler的拦截主要发生在setParameters()将Java参数转换为JDBC参数时这个拦截点常用于实现参数加密/解密参数格式校验动态参数注入3.4 结果集处理器(ResultSetHandler)拦截点ResultSetHandler的拦截时机包括handleResultSets()处理结果集时handleOutputParameters()处理存储过程输出参数时这里可以实现结果集数据脱敏自定义类型转换结果集后处理4. 实战自定义SQL执行耗时统计拦截器下面通过一个完整的示例展示如何实现一个统计SQL执行耗时的拦截器Intercepts({ Signature(type StatementHandler.class, method query, args {Statement.class, ResultHandler.class}), Signature(type StatementHandler.class, method update, args {Statement.class}) }) public class SqlCostInterceptor implements Interceptor { private static final Logger logger LoggerFactory.getLogger(SqlCostInterceptor.class); Override public Object intercept(Invocation invocation) throws Throwable { long start System.currentTimeMillis(); try { return invocation.proceed(); } finally { long cost System.currentTimeMillis() - start; Statement statement (Statement) invocation.getArgs()[0]; logger.info(SQL执行耗时[{}ms] - {}, cost, statement.getConnection().nativeSQL(statement.toString())); } } Override public Object plugin(Object target) { return Plugin.wrap(target, this); } Override public void setProperties(Properties properties) { // 可接收配置参数 } }在mybatis-config.xml中配置plugins plugin interceptorcom.example.SqlCostInterceptor property namethreshold value500/ /plugin /plugins这个拦截器会记录每个SQL语句的执行时间当日志级别为DEBUG时还会输出完整的SQL语句。实际项目中可以扩展实现慢SQL报警等功能。5. 拦截器开发中的常见陷阱5.1 拦截器执行顺序问题由于MyBatis的拦截器是嵌套代理模式开发者经常混淆执行顺序。记住这个原则配置顺序A - B - C执行顺序C - B - A如果需要严格控制执行顺序可以考虑使用Order注解或实现Ordered接口需结合Spring。5.2 过度拦截导致的性能问题每个拦截器都会增加方法调用的栈深度过度使用会导致调用链路过长反射开销累积内存占用增加建议只拦截必要的方法避免在拦截器中处理耗时操作合并功能相似的拦截器5.3 线程安全问题拦截器默认是单例的因此不要定义实例变量必须线程安全地操作共享资源谨慎使用ThreadLocal5.4 与PageHelper等插件的冲突常见冲突场景多个插件拦截同一个方法插件修改了同一SQL参数插件对结果集的二次处理解决方案调整插件配置顺序明确各插件的职责边界必要时修改插件源码6. 高级拦截器应用场景6.1 动态数据源路由通过拦截Executor的query/update方法可以根据参数动态切换数据源Intercepts({ Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class DynamicDataSourceInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { Object parameter invocation.getArgs()[1]; String dsKey determineDataSourceKey(parameter); DynamicDataSourceContextHolder.setDataSourceKey(dsKey); try { return invocation.proceed(); } finally { DynamicDataSourceContextHolder.clearDataSourceKey(); } } // 其他方法省略... }6.2 SQL审计与合规检查结合StatementHandler拦截器可以实现SQL注入检测敏感字段访问监控高危操作拦截public Object intercept(Invocation invocation) throws Throwable { Statement statement (Statement) invocation.getArgs()[0]; String sql statement.toString(); if (containsSensitiveDataAccess(sql)) { auditService.logSensitiveAccess(currentUser(), sql); } if (isDangerousOperation(sql)) { throw new SecurityException(危险操作被拦截); } return invocation.proceed(); }6.3 分布式链路追踪集成在微服务架构下可以通过拦截器自动注入追踪信息public Object intercept(Invocation invocation) throws Throwable { Span span tracer.buildSpan(mybatis-query).start(); try (Scope scope tracer.activateSpan(span)) { span.setTag(sql.method, invocation.getMethod().getName()); return invocation.proceed(); } catch (Exception e) { span.log(e.getMessage()); throw e; } finally { span.finish(); } }这种实现方式可以无缝对接Jaeger、Zipkin等分布式追踪系统。7. 性能优化建议7.1 减少不必要的拦截通过精确指定Signature来缩小拦截范围// 不推荐 - 拦截所有方法 Signature(type StatementHandler.class, method *, args {}) // 推荐 - 只拦截目标方法 Signature(type StatementHandler.class, method query, args {...})7.2 使用缓存减少重复计算对于耗时的预处理操作可以考虑使用缓存private final CacheString, Object preparationCache Caffeine.newBuilder().maximumSize(1000).build(); public Object intercept(Invocation invocation) throws Throwable { String cacheKey generateCacheKey(invocation); return preparationCache.get(cacheKey, k - { try { return doIntercept(invocation); } catch (Throwable e) { throw new RuntimeException(e); } }); }7.3 批量操作优化对于批量操作可以设计特殊的拦截逻辑Intercepts({ Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}) }) public class BatchOptimizeInterceptor implements Interceptor { private final ThreadLocalBatchContext batchContext new ThreadLocal(); public Object intercept(Invocation invocation) throws Throwable { if (isBatchOperation(invocation)) { BatchContext context batchContext.get(); if (context null) { context new BatchContext(); batchContext.set(context); } return handleBatch(invocation, context); } return invocation.proceed(); } // 其他方法省略... }这种模式可以显著提升批量插入/更新操作的性能。