org.apache.ibatis.plugin.Interceptor 是 MyBatis 插件体系的核心扩展接口,MyBatis 提供的插件机制完全基于这个接口实现——它允许开发者通过动态代理拦截 MyBatis 核心执行流程中的关键方法,在不修改框架源码的前提下,对 MyBatis 的执行逻辑做自定义增强(如分页、SQL 日志、性能监控、权限控制、参数加密/结果解密等)。
简单来说:所有自定义的 MyBatis 插件,都必须实现这个 Interceptor 接口,并通过注解+配置声明拦截规则,MyBatis 会在启动时加载插件,通过动态代理为目标组件生成代理对象,执行目标方法时触发插件的拦截逻辑。
一、核心作用
MyBatis 的插件本质是对四大核心执行组件的动态代理增强,Interceptor 接口的核心作用就是定义拦截的统一规范,包含「拦截规则匹配」「核心拦截逻辑」「插件参数配置」三大能力,具体落地为:
- 拦截 MyBatis 执行过程中的关键节点(如 SQL 执行前、参数处理后、结果集返回前);
- 对拦截到的方法做前置/后置增强(如执行前修改 SQL、执行后记录耗时);
- 支持从配置文件中接收自定义参数,让插件更灵活;
- 遵循责任链模式,多个插件可按配置顺序依次执行拦截逻辑。
二、底层拦截原理:MyBatis 仅支持拦截四大核心组件
MyBatis 的插件机制并非能拦截所有方法,而是仅对框架内的 4 个核心执行组件的特定方法开放拦截(这是 MyBatis 源码硬编码的规则),所有插件的拦截目标都必须是这 4 个组件的指定方法,核心原因是:MyBatis 启动时,会为这 4 个组件创建实例,并通过 Interceptor 生成代理对象,替换原实例。
可拦截的四大核心组件(必记)
| 组件类 | 核心作用 | 典型拦截场景 |
|---|---|---|
Executor |
SQL 执行器(核心),负责增删改查的执行、缓存管理 | 全局分页、SQL 执行性能监控、缓存自定义 |
StatementHandler |
数据库语句(Statement)处理器,负责 SQL 构建、参数设置、Statement 执行 | 动态修改 SQL、分页拼接、SQL 日志打印 |
ParameterHandler |
参数处理器,负责将 Java 参数转换为 JDBC 可识别的参数 | 参数加密、空值参数默认值设置 |
ResultSetHandler |
结果集处理器,负责将 JDBC 结果集转换为 Java 对象 | 结果解密、数据脱敏、结果集二次处理 |
注:这 4 个组件都有对应的接口和实现类(如
Executor有SimpleExecutor/ReuseExecutor/BatchExecutor),插件拦截的是接口,而非具体实现类。
三、Interceptor 接口核心方法详解
该接口仅有 3 个抽象方法,所有自定义插件都必须重写,三者分工明确,构成插件的完整生命周期:
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
// 核心:拦截逻辑的实现方法
Object intercept(Invocation invocation) throws Throwable;
// 生成目标对象的代理对象(MyBatis 调用,用于将插件织入目标对象)
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 加载插件时,设置配置文件中的自定义参数(可选)
default void setProperties(Properties properties) {
// 空实现,子类可重写
}
}
1. Object intercept(Invocation invocation) throws Throwable
插件的核心方法,所有自定义的拦截逻辑都写在这里,MyBatis 执行被拦截的方法时,会回调这个方法。
- 参数
Invocation:MyBatis 封装的「调用信息对象」,核心作用是获取目标对象、目标方法、方法参数,并执行原目标方法,关键方法:Object getTarget():获取被拦截的目标组件实例(如StatementHandler、Executor);Method getMethod():获取被拦截的目标方法(如StatementHandler的prepare方法);Object[] getArgs():获取被拦截方法的参数数组;Object proceed() throws Throwable:执行原目标方法(核心!如果不调用这个方法,原 MyBatis 逻辑会被阻断)。
- 返回值:原目标方法的执行结果,可自定义修改后返回(如修改结果集、修改返回值)。
- 异常:可抛出任意异常,会被 MyBatis 上层捕获并处理(如 SQL 执行异常)。
2. default Object plugin(Object target)
生成代理对象的方法,MyBatis 框架会在创建四大核心组件时调用此方法,判断当前插件是否需要拦截该目标对象,若需要则生成动态代理对象(替换原组件实例),若不需要则直接返回原对象。
- 默认实现:
Plugin.wrap(target, this),这是 MyBatis 提供的官方推荐实现,底层基于 JDK 动态代理(Plugin类是 MyBatis 插件的动态代理核心类,实现了InvocationHandler),无需自定义重写,直接使用默认实现即可。 - 参数
target:MyBatis 传入的四大核心组件的实例(如SimpleExecutor、RoutingStatementHandler)。 - 返回值:若当前插件拦截该目标对象,则返回代理对象;否则返回原
target对象。
3. default void setProperties(Properties properties)
插件参数初始化方法,MyBatis 加载插件时(如启动时解析 XML/Spring 配置),会将配置文件中为该插件设置的自定义参数传入此方法,子类可重写该方法接收参数并初始化。
- 参数
Properties:键值对形式的参数集合,与配置文件中的参数一一对应; - 使用场景:让插件支持可配置化(如分页插件的默认页码、页大小,日志插件的打印格式);
- 默认实现:空方法,若插件无需接收参数,可不用重写。
四、拦截规则注解:@Intercepts + @Signature(必配)
实现 Interceptor 接口后,还需要通过 MyBatis 专属注解声明拦截规则——告诉 MyBatis 「当前插件要拦截哪个组件、哪个方法、方法的参数类型是什么」,否则 MyBatis 无法识别插件的拦截目标。
这两个注解都在 org.apache.ibatis.plugin 包下,必须组合使用:
1. 核心注解:@Intercepts
作用:标记当前类是 MyBatis 插件,并包裹一个或多个 @Signature 注解,声明具体的拦截规则(一个插件可同时拦截多个组件的多个方法)。
- 唯一属性:
Signature[] value(),数组类型,存放具体的拦截签名。
2. 拦截签名:@Signature
作用:声明单个拦截规则,明确「拦截哪个组件、哪个方法、方法的参数类型」,三个属性缺一不可,且必须与目标方法的定义完全一致(否则拦截失败)。
| 属性 | 类型 | 作用 |
|---|---|---|
type |
Class<?> |
要拦截的核心组件接口(必须是四大组件之一:Executor/StatementHandler/ParameterHandler/ResultSetHandler) |
method |
String |
要拦截的目标方法名(必须是 type 接口中的方法名,大小写敏感) |
args |
Class<?>[] |
目标方法的参数类型数组(必须与目标方法的参数类型、顺序完全一致) |
注解使用示例(关键)
比如要拦截 StatementHandler 的 prepare 方法(该方法负责预编译 SQL,是分页、修改 SQL 的核心切入点),先查看 StatementHandler 中 prepare 方法的源码定义:
// StatementHandler 接口中的 prepare 方法
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
则对应的拦截注解为:
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.executor.statement.StatementHandler;
// 标记为MyBatis插件,声明拦截规则
@Intercepts({
@Signature(
type = StatementHandler.class, // 拦截StatementHandler组件
method = "prepare", // 拦截prepare方法
args = {Connection.class, Integer.class} // 方法参数类型(顺序一致)
)
})
public class MySqlPlugin implements Interceptor {
// 实现接口方法...
}
重要注意:
args的参数类型必须是接口中定义的原始类型(如Connection是java.sql.Connection,不能用子类),且顺序必须完全一致,否则 MyBatis 无法匹配到目标方法,插件不会生效。
五、自定义 MyBatis 插件的完整步骤
以实现一个 SQL 执行性能监控插件为例(拦截 StatementHandler 的 prepare 方法,记录 SQL 执行耗时),完整演示插件的开发、配置、使用流程,新手可直接跟着实现。
步骤 1:导入 MyBatis 核心依赖
如果是纯 MyBatis 项目,引入核心包;如果是Spring Boot + MyBatis 项目,引入整合包(推荐):
<!-- Spring Boot + MyBatis 整合包(主流) -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- 数据库驱动(以MySQL为例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
步骤 2:实现 Interceptor 接口 + 声明拦截规则
编写插件类,实现接口并通过 @Intercepts+@Signature 声明拦截规则,核心在 intercept 方法中实现耗时统计逻辑:
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.util.Properties;
/**
* MyBatis插件:SQL执行性能监控(拦截StatementHandler的prepare方法,记录SQL执行耗时)
*/
// 声明拦截规则:拦截StatementHandler的prepare方法
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class SqlPerformanceInterceptor implements Interceptor {
// 日志打印
private static final Logger log = LoggerFactory.getLogger(SqlPerformanceInterceptor.class);
// 自定义参数:慢SQL阈值(单位:ms),默认500ms
private long slowSqlThreshold = 500;
/**
* 核心拦截逻辑:记录SQL执行开始时间,执行后计算耗时,打印日志
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取目标对象:StatementHandler(SQL处理器)
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 2. 获取当前执行的SQL语句(MyBatis已完成参数解析的最终SQL)
String sql = statementHandler.getBoundSql().getSql().replaceAll("\\s+", " ");
// 3. 记录SQL执行开始时间
long startTime = System.currentTimeMillis();
try {
// 执行原目标方法(必须调用,否则SQL不会执行)
return invocation.proceed();
} finally {
// 4. 计算执行耗时(finally保证无论是否异常都能统计)
long costTime = System.currentTimeMillis() - startTime;
// 5. 打印日志:慢SQL标红提示
if (costTime > slowSqlThreshold) {
log.warn("【慢SQL警告】执行耗时:{}ms,SQL:{}", costTime, sql);
} else {
log.info("【SQL执行】执行耗时:{}ms,SQL:{}", costTime, sql);
}
}
}
/**
* 接收配置文件中的自定义参数(如慢SQL阈值)
*/
@Override
public void setProperties(Properties properties) {
// 从properties中获取slowSqlThreshold参数,若配置则覆盖默认值
String threshold = properties.getProperty("slowSqlThreshold");
if (threshold != null && !threshold.isEmpty()) {
this.slowSqlThreshold = Long.parseLong(threshold);
}
}
// plugin方法使用默认实现(Plugin.wrap),无需重写
}
步骤 3:配置插件(分两种场景,二选一)
MyBatis 插件需要在启动时被框架加载,配置方式分纯 MyBatis 项目(XML 配置)和Spring Boot + MyBatis 项目(注解配置),主流是后者。
场景 1:Spring Boot + MyBatis 项目(推荐,注解配置)
通过 @Configuration 配置类,将插件实例注入 Spring 容器,MyBatis-Spring 会自动将其注册为 MyBatis 插件,支持设置自定义参数:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
@MapperScan("com.xxx.mapper") // 扫描Mapper接口
public class MyBatisConfig {
/**
* 注册SQL性能监控插件
*/
@Bean
public SqlPerformanceInterceptor sqlPerformanceInterceptor() {
SqlPerformanceInterceptor plugin = new SqlPerformanceInterceptor();
// 设置自定义参数:慢SQL阈值为1000ms
Properties properties = new Properties();
properties.setProperty("slowSqlThreshold", "1000");
plugin.setProperties(properties);
return plugin;
}
}
场景 2:纯 MyBatis 项目(XML 配置,mybatis-config.xml)
在 MyBatis 核心配置文件中通过 <plugins> 标签配置插件,property 标签设置自定义参数:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置MyBatis插件 -->
<plugins>
<plugin interceptor="com.xxx.plugin.SqlPerformanceInterceptor">
<!-- 自定义参数:慢SQL阈值1000ms -->
<property name="slowSqlThreshold" value="1000"/>
</plugin>
</plugins>
<!-- 其他配置:environments、mappers等 -->
</configuration>
步骤 4:测试插件
启动项目,执行任意数据库操作(如Mapper的查询/新增),控制台会打印 SQL 执行耗时日志,慢于 1000ms 的 SQL 会触发慢SQL警告,效果如下:
【SQL执行】执行耗时:12ms,SQL:select id, name, age from user where id = ?
【慢SQL警告】执行耗时:1200ms,SQL:select * from order where create_time between ? and ?
六、多插件执行顺序:责任链模式
如果配置了多个 MyBatis 插件(如同时有分页插件、性能监控插件、数据脱敏插件),MyBatis 会按照配置的顺序为目标组件生成多层动态代理对象,执行目标方法时,会从外到内依次触发插件的 intercept 方法,执行完成后从内到外返回结果,这就是责任链模式。
示例:配置两个插件
// 配置顺序1:性能监控插件
@Bean
public SqlPerformanceInterceptor sqlPerformanceInterceptor() { ... }
// 配置顺序2:数据脱敏插件
@Bean
public DataDesensitizeInterceptor dataDesensitizeInterceptor() { ... }
执行流程:
代理对象2(脱敏) → 代理对象1(性能监控) → 原StatementHandler → 执行SQL → 原StatementHandler返回 → 代理对象1处理 → 代理对象2处理 → 最终结果返回。
注意:若需要调整插件执行顺序,直接调整配置类中
@Bean的声明顺序(Spring Boot)或 XML 中<plugin>的顺序(纯 MyBatis)即可。
七、使用注意事项(避坑关键)
- 仅拦截四大核心组件:MyBatis 源码仅对 Executor、StatementHandler、ParameterHandler、ResultSetHandler 做了插件代理处理,拦截其他类/方法会无效;
- 注解参数必须完全匹配:
@Signature的method和args必须与目标接口的方法定义完全一致(方法名大小写、参数类型/顺序、个数),否则插件不会生效; - 必须调用 invocation.proceed():
intercept方法中若不调用该方法,原 MyBatis 执行逻辑会被阻断,导致 SQL 不执行、参数不处理等问题; - 避免过度拦截:插件基于动态代理实现,多层代理会带来轻微性能损耗,建议仅拦截必要的方法,避免拦截所有方法;
- 慎用修改原对象:拦截时可修改目标方法的参数(如
invocation.getArgs())或返回值,但需保证修改后符合 MyBatis 逻辑,避免框架异常; - 处理线程安全:MyBatis 插件实例是单例的,若插件中有成员变量,需保证线程安全(如使用线程安全的容器、避免修改成员变量);
- 兼容MyBatis版本:不同 MyBatis 版本的四大组件方法可能有细微调整(如 MyBatis 3.5+ 对 StatementHandler 的方法做了小修改),插件需适配项目的 MyBatis 版本;
- 避免与第三方插件冲突:若项目已使用分页插件(如 PageHelper)、通用 Mapper 等第三方 MyBatis 插件,需注意拦截规则是否冲突(如同时拦截 Executor 的 query 方法),可通过调整执行顺序解决。
八、典型应用场景
Interceptor 接口的核心价值是无侵入式扩展 MyBatis,企业开发中常见的插件场景:
- 分页插件:如 PageHelper,拦截 Executor 的 query 方法,动态拼接分页 SQL(limit/rownum);
- SQL 日志/性能监控:拦截 StatementHandler 的 prepare 方法,记录 SQL、执行耗时、慢 SQL 告警;
- 参数处理:拦截 ParameterHandler 的 setParameters 方法,实现参数加密、空值默认值设置、参数校验;
- 结果集处理:拦截 ResultSetHandler 的 handleResultSets 方法,实现数据脱敏、结果集转换、权限过滤;
- SQL 拦截/修改:拦截 StatementHandler 的 prepare 方法,动态修改 SQL(如添加租户标识、数据权限过滤);
- 事务增强:拦截 Executor 的 update/query 方法,实现自定义事务控制、分布式事务埋点。
总结
Interceptor是 MyBatis 插件体系的核心扩展接口,所有自定义插件必须实现该接口,核心作用是无侵入式增强 MyBatis 执行逻辑;- 插件的底层是动态代理,仅对 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大核心组件开放拦截;
- 插件的拦截规则由
@Intercepts+@Signature声明,必须明确「拦截组件、方法名、参数类型」,且参数需与目标方法完全匹配; - 接口的三个方法分工:
intercept实现核心拦截逻辑(必须调用proceed())、plugin生成代理对象(用默认Plugin.wrap)、setProperties接收自定义参数; - 插件配置分 Spring Boot 注解(
@Bean)和纯 MyBatis XML(<plugins>),多插件按配置顺序执行(责任链模式); - 核心应用场景:分页、SQL 监控、参数/结果集处理、数据脱敏等,是企业级 MyBatis 开发的必备扩展能力。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接
文章评论