李锋镝的博客

  • 首页
  • 时间轴
  • 评论区显眼包🔥
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
  • 留言
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 后端
  3. 正文

org.apache.ibatis.plugin.Interceptor类详细介绍及使用

2026年2月4日 25点热度 0人点赞 0条评论

org.apache.ibatis.plugin.Interceptor 是 MyBatis 插件体系的核心扩展接口,MyBatis 提供的插件机制完全基于这个接口实现——它允许开发者通过动态代理拦截 MyBatis 核心执行流程中的关键方法,在不修改框架源码的前提下,对 MyBatis 的执行逻辑做自定义增强(如分页、SQL 日志、性能监控、权限控制、参数加密/结果解密等)。

简单来说:所有自定义的 MyBatis 插件,都必须实现这个 Interceptor 接口,并通过注解+配置声明拦截规则,MyBatis 会在启动时加载插件,通过动态代理为目标组件生成代理对象,执行目标方法时触发插件的拦截逻辑。

一、核心作用

MyBatis 的插件本质是对四大核心执行组件的动态代理增强,Interceptor 接口的核心作用就是定义拦截的统一规范,包含「拦截规则匹配」「核心拦截逻辑」「插件参数配置」三大能力,具体落地为:

  1. 拦截 MyBatis 执行过程中的关键节点(如 SQL 执行前、参数处理后、结果集返回前);
  2. 对拦截到的方法做前置/后置增强(如执行前修改 SQL、执行后记录耗时);
  3. 支持从配置文件中接收自定义参数,让插件更灵活;
  4. 遵循责任链模式,多个插件可按配置顺序依次执行拦截逻辑。

二、底层拦截原理: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)即可。

七、使用注意事项(避坑关键)

  1. 仅拦截四大核心组件:MyBatis 源码仅对 Executor、StatementHandler、ParameterHandler、ResultSetHandler 做了插件代理处理,拦截其他类/方法会无效;
  2. 注解参数必须完全匹配:@Signature 的 method 和 args 必须与目标接口的方法定义完全一致(方法名大小写、参数类型/顺序、个数),否则插件不会生效;
  3. 必须调用 invocation.proceed():intercept 方法中若不调用该方法,原 MyBatis 执行逻辑会被阻断,导致 SQL 不执行、参数不处理等问题;
  4. 避免过度拦截:插件基于动态代理实现,多层代理会带来轻微性能损耗,建议仅拦截必要的方法,避免拦截所有方法;
  5. 慎用修改原对象:拦截时可修改目标方法的参数(如 invocation.getArgs())或返回值,但需保证修改后符合 MyBatis 逻辑,避免框架异常;
  6. 处理线程安全:MyBatis 插件实例是单例的,若插件中有成员变量,需保证线程安全(如使用线程安全的容器、避免修改成员变量);
  7. 兼容MyBatis版本:不同 MyBatis 版本的四大组件方法可能有细微调整(如 MyBatis 3.5+ 对 StatementHandler 的方法做了小修改),插件需适配项目的 MyBatis 版本;
  8. 避免与第三方插件冲突:若项目已使用分页插件(如 PageHelper)、通用 Mapper 等第三方 MyBatis 插件,需注意拦截规则是否冲突(如同时拦截 Executor 的 query 方法),可通过调整执行顺序解决。

八、典型应用场景

Interceptor 接口的核心价值是无侵入式扩展 MyBatis,企业开发中常见的插件场景:

  1. 分页插件:如 PageHelper,拦截 Executor 的 query 方法,动态拼接分页 SQL(limit/rownum);
  2. SQL 日志/性能监控:拦截 StatementHandler 的 prepare 方法,记录 SQL、执行耗时、慢 SQL 告警;
  3. 参数处理:拦截 ParameterHandler 的 setParameters 方法,实现参数加密、空值默认值设置、参数校验;
  4. 结果集处理:拦截 ResultSetHandler 的 handleResultSets 方法,实现数据脱敏、结果集转换、权限过滤;
  5. SQL 拦截/修改:拦截 StatementHandler 的 prepare 方法,动态修改 SQL(如添加租户标识、数据权限过滤);
  6. 事务增强:拦截 Executor 的 update/query 方法,实现自定义事务控制、分布式事务埋点。

总结

  1. Interceptor 是 MyBatis 插件体系的核心扩展接口,所有自定义插件必须实现该接口,核心作用是无侵入式增强 MyBatis 执行逻辑;
  2. 插件的底层是动态代理,仅对 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大核心组件开放拦截;
  3. 插件的拦截规则由 @Intercepts+@Signature 声明,必须明确「拦截组件、方法名、参数类型」,且参数需与目标方法完全匹配;
  4. 接口的三个方法分工:intercept 实现核心拦截逻辑(必须调用 proceed())、plugin 生成代理对象(用默认 Plugin.wrap)、setProperties 接收自定义参数;
  5. 插件配置分 Spring Boot 注解(@Bean)和纯 MyBatis XML(<plugins>),多插件按配置顺序执行(责任链模式);
  6. 核心应用场景:分页、SQL 监控、参数/结果集处理、数据脱敏等,是企业级 MyBatis 开发的必备扩展能力。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/hou-duan/4688

相关文章

  • 使用内存数据库进行MyBatis单元测试
  • 深度解析多级缓存架构:从设计到落地,彻底解决数据一致性难题
  • MyBatis vs Spring Data JPA 从原理到实战全解析
  • mybatis-plus-join-boot-starter介绍及用法
  • JDK25模块级导入深度解析:Java导入机制的革命性进化
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: Interceptor MyBatis MySQL
最后更新:2026年2月4日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇

文章评论

1 2 3 4 5 6 7 8 9 11 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 46 47 48 49 50 51 52 53 54 55 57 58 60 61 62 63 64 65 66 67 69 72 74 76 77 78 79 80 81 82 85 86 87 90 92 93 94 95 96 97 98 99
取消回复

秋天是倒放的春天,晚安是爱你的序篇。

那年今日(02月10日)

  • 1953年:穆罕默德·纳吉布出任埃及总统
  • 1923年:德国物理学家、X射线发现者伦琴逝世
  • 1898年:德国戏剧家贝尔托·布莱希特出生
  • 1894年:英国政治家哈罗德·麦克米伦出生
  • 589年:杨坚灭陈朝,南北朝结束
  • 更多历史事件
最新 热点 随机
最新 热点 随机
Apollo配置中心中的protalDB的作用是什么 org.apache.ibatis.plugin.Interceptor类详细介绍及使用 JDK25模块级导入深度解析:Java导入机制的革命性进化 AI时代,个人技术博客的出路在哪里? 什么是Meta Server? 千万级大表新增字段实战指南:告别锁表与业务中断
玩博客的人是不是越来越少了?AI时代,个人技术博客的出路在哪里?准备入手个亚太的ECS,友友们有什么建议吗?使用WireGuard在Ubuntu 24.04系统搭建VPNWordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了
JWT、Cookie、Session、Token 区别与实战选型指南 Spring Boot 2.5.0重新设计的spring.sql.init 配置有啥用? 微服务的数据库设计 MySQL数据库详解——执行SQL更新时,其底层经历了哪些操作? AI重构开发者工作范式:从Anthropic内部调研看Claude对研发领域的深层影响 使用Spring MVC的websocket配置时 Tomcat启动报错
标签聚合
Spring K8s docker JAVA JVM 分布式 数据库 SpringBoot AI IDEA Redis 日常 AI编程 MySQL 多线程 SQL 设计模式 WordPress ElasticSearch 架构
友情链接
  • Blogs·CN
  • Honesty
  • Mr.Sun的博客
  • 临窗旋墨
  • 哥斯拉
  • 彬红茶日记
  • 志文工作室
  • 懋和道人
  • 搬砖日记
  • 旧时繁华
  • 林羽凡
  • 瓦匠个人小站
  • 皮皮社
  • 知向前端
  • 蜗牛工作室
  • 韩小韩博客
  • 风渡言

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

域名年龄

Theme Kratos Made By Dylan

津ICP备2024022503号-3

京公网安备11011502039375号