Mybatis拦截器原理解析

news/2025/12/8 21:22:54/文章来源:https://www.cnblogs.com/ciel717/p/19321762

一、概述

在Mybatis开发体系中,“过滤器”是开发者对Mybatis拦截器(Plugin)的通俗称呼,它是Mybatis提供的核心扩展机制——无需修改框架源码,就能对SQL执行全流程进行增强,比如分页插件、通用字段自动填充、数据加解密等功能,底层均依赖这一机制。本文将从底层技术原理、核心源码解析到实战开发,完整拆解Mybatis过滤器的实现逻辑,还原其设计精髓。

二、核心对象

2.1 可拦截的核心接口

MyBatis仅允许拦截其内部4个核心接口的方法,这4个接口覆盖了SQL执行的全流程,开发者需通过@Intercepts注解指定拦截目标:

拦截接口 核心作用 常用拦截方法 典型应用场景
Executor SQL执行器(增删改查入口) query/update/commit 耗时统计、分页插件、数据权限控制
ParameterHandler 参数处理器(设置SQL预编译参数) setParameters 参数加密、参数校验、默认值填充
ResultSetHandler 结果集处理器(封装查询结果) handleResultSets 结果集解密、数据脱敏、格式转换
StatementHandler SQL语句处理器(创建Statement对象) prepare/parameterize SQL改写、SQL日志打印、防注入处理

2.2 拦截器核心接口

自定义拦截器必须实现org.apache.ibatis.plugin.Interceptor接口,该接口包含3个核心方法:

方法名 作用说明
intercept(Invocation invocation) 核心拦截逻辑入口,SQL执行时触发,可修改参数、增强结果、统计信息等
plugin(Object target) 为目标对象创建动态代理,推荐使用MyBatis提供的Plugin.wrap()方法实现
setProperties(Properties properties) 读取拦截器配置属性(如从配置文件传入的密钥、日志级别等),可选实现

2.3 关键注解

  1. @Intercepts:标识该类为MyBatis拦截器,内部可包含多个@Signature注解,指定拦截的接口、方法和参数。
  2. @Signature:定义具体的拦截规则,包含3个属性:
    • type:拦截的核心接口(如Executor.class);
    • method:拦截的方法名(如query);
    • args:拦截方法的参数类型数组(必须与接口方法参数完全匹配)。

三、工作流程

MyBatis拦截器的执行遵循“动态代理+责任链”模式,完整流程如下:

  1. 初始化阶段:Spring Boot启动时,通过配置类将自定义拦截器注册到SqlSessionFactory
  2. 代理创建阶段:MyBatis初始化核心接口(如Executor)时,调用拦截器的plugin()方法,为目标接口创建动态代理;
  3. 方法拦截阶段:执行Mapper方法触发SQL时,代理对象拦截目标方法调用,进入intercept()方法;
  4. 自定义逻辑执行:在intercept()中执行前置增强(如记录开始时间、修改参数);
  5. 原方法执行:通过invocation.proceed()调用被拦截的原方法,执行SQL操作;
  6. 后置处理阶段:原方法执行完成后,执行后置增强(如统计耗时、解密结果集);
  7. 结果返回:将处理后的结果返回给调用者。

若注册了多个拦截器,将按“注册顺序逆序执行”(责任链模式),例如注册顺序为拦截器A→拦截器B→拦截器C,实际执行顺序为拦截器C→拦截器B→拦截器A

四、底层实现

Mybatis过滤器的实现依赖两大基础技术,我们先通过极简示例还原核心逻辑,再对接Mybatis源码。

4.1 基础:JDK动态代理核心实现

JDK动态代理是过滤器的“增强载体”,通过InvocationHandler对目标对象方法进行前置/后置增强,先看基础源码实现:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 目标接口(模拟Mybatis核心接口)
public interface Executor {void query(String sql);
}// 目标实现类(模拟Mybatis默认Executor)
public class SimpleExecutor implements Executor {@Overridepublic void query(String sql) {System.out.println("执行SQL:" + sql);}
}// 动态代理处理器(核心增强逻辑)
public class PluginInvocationHandler implements InvocationHandler {// 目标对象(如Executor实例)private final Object target;// 拦截器(过滤器)实例private final Interceptor interceptor;public PluginInvocationHandler(Object target, Interceptor interceptor) {this.target = target;this.interceptor = interceptor;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1. 执行过滤器的拦截逻辑Object result = interceptor.intercept(new Invocation(target, method, args));return result;}// 生成代理对象的工具方法public static Object wrap(Object target, Interceptor interceptor) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new PluginInvocationHandler(target, interceptor));}
}// 抽象过滤器(拦截器)接口
public interface Interceptor {// 核心拦截方法Object intercept(Invocation invocation) throws Throwable;// 为目标对象创建代理(绑定过滤器)default Object plugin(Object target) {return PluginInvocationHandler.wrap(target, this);}
}// 方法调用封装类(封装目标对象、方法、参数)
public class Invocation {private final Object target;private final Method method;private final Object[] args;public Invocation(Object target, Method method, Object[] args) {this.target = target;this.method = method;this.args = args;}// 执行目标方法public Object proceed() throws Throwable {return method.invoke(target, args);}// getter方法public Object getTarget() { return target; }public Method getMethod() { return method; }public Object[] getArgs() { return args; }
}

4.2 进阶:责任链模式管理多过滤器

当存在多个过滤器时,需要通过“拦截器链”管理执行顺序,这是责任链模式的典型应用,源码如下:

import java.util.ArrayList;
import java.util.List;// 过滤器链(拦截器链)
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;}// 添加过滤器public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}// 获取所有过滤器public List<Interceptor> getInterceptors() {return new ArrayList<>(interceptors);}
}// 测试:多过滤器链式执行
public class TestProxy {public static void main(String[] args) throws Throwable {// 1. 创建目标对象Executor target = new SimpleExecutor();// 2. 创建过滤器链并注册过滤器InterceptorChain chain = new InterceptorChain();// 过滤器1:日志增强chain.addInterceptor(new Interceptor() {@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("【日志过滤器】前置增强:执行SQL前打印日志");Object result = invocation.proceed();System.out.println("【日志过滤器】后置增强:执行SQL后打印日志");return result;}});// 过滤器2:性能监控chain.addInterceptor(new Interceptor() {@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();Object result = invocation.proceed();long end = System.currentTimeMillis();System.out.println("【性能过滤器】SQL执行耗时:" + (end - start) + "ms");return result;}});// 3. 绑定所有过滤器,生成代理对象Executor proxyExecutor = (Executor) chain.pluginAll(target);// 4. 执行目标方法proxyExecutor.query("SELECT * FROM user WHERE id = 1");}
}

执行结果:

【日志过滤器】前置增强:执行SQL前打印日志
【性能过滤器】前置增强:开始计时
执行SQL:SELECT * FROM user WHERE id = 1
【性能过滤器】后置增强:SQL执行耗时:0ms
【日志过滤器】后置增强:执行SQL后打印日志

上述示例完整还原了Mybatis过滤器的核心逻辑:通过JDK动态代理实现单方法增强,通过责任链模式实现多过滤器的链式执行。

五、核心源码解析

Mybatis源码中对过滤器的实现与上述示例高度一致,以下是核心类的源码拆解:

5.1 核心接口:Interceptor

Mybatis定义的过滤器核心接口,所有自定义过滤器必须实现此接口:

package org.apache.ibatis.plugin;import java.util.Properties;public interface Interceptor {// 核心拦截方法:编写自定义增强逻辑Object intercept(Invocation invocation) throws Throwable;// 为目标对象创建代理对象default Object plugin(Object target) {return Plugin.wrap(target, this);}// 设置过滤器配置属性(从mybatis-config.xml读取)default void setProperties(Properties properties) {// 空实现,可自定义覆盖}
}

5.2 代理核心类:Plugin

Mybatis内置的InvocationHandler实现类,负责动态代理的创建和方法拦截判断:

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;public class Plugin implements InvocationHandler {// 目标对象(如Executor、StatementHandler实例)private final Object target;// 当前过滤器实例private final Interceptor interceptor;// 过滤器注解配置的拦截方法映射(@Intercepts + @Signature)private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}// 生成代理对象的核心方法public static Object wrap(Object target, Interceptor interceptor) {// 解析过滤器的@Intercepts注解,获取需要拦截的方法Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();// 获取目标对象的所有接口(仅拦截Mybatis四大核心接口)Class<?>[] interfaces = getAllInterfaces(type, signatureMap);// 存在可拦截接口则生成代理,否则返回原对象if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}// 代理方法执行入口:判断是否拦截当前方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 获取当前方法所属接口的拦截方法集合Set<Method> methods = signatureMap.get(method.getDeclaringClass());// 若当前方法需要拦截,则执行过滤器逻辑if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));}// 无需拦截则直接执行原方法return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}// 解析@Intercepts注解,生成拦截方法映射private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// 无@Intercepts注解则抛出异常if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor "+ interceptor.getClass().getName());}Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();for (Signature sig : sigs) {Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());try {Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type()+ " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}// 获取目标对象的所有可拦截接口private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<>();while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}return interfaces.toArray(new Class<?>[0]);}
}

5.3 过滤器链:InterceptorChain

Mybatis用于管理所有注册过滤器的核心类,负责为目标对象绑定所有过滤器:

package org.apache.ibatis.plugin;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;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;}// 添加过滤器public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}// 获取不可变的过滤器列表public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}
}

5.4 初始化入口:Configuration

Mybatis在创建四大核心接口实例时,会通过InterceptorChain绑定所有过滤器,以Executor为例:

package org.apache.ibatis.session;import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.ReuseExecutor;
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.plugin.InterceptorChain;public class Configuration {// 过滤器链实例protected final InterceptorChain interceptorChain = new InterceptorChain();// 创建Executor实例并绑定过滤器public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);}public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;// 创建不同类型的Executor实例if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}// 开启缓存则包装为CachingExecutorif (cacheEnabled) {executor = new CachingExecutor(executor);}// 为Executor绑定所有过滤器(核心步骤)executor = (Executor) interceptorChain.pluginAll(executor);return executor;}// ParameterHandler/StatementHandler/ResultSetHandler创建逻辑同理// ...
}

六、使用注意事项

  • 拦截范围限制:仅能拦截ExecutorParameterHandlerStatementHandlerResultSetHandler四大接口的方法,非核心接口无法拦截;
  • 注解配置规范:@Signature中的method必须与目标接口方法名一致,args必须与方法参数类型完全匹配(顺序、类型均不能错);
  • 性能影响:每一次拦截都会触发动态代理的invoke方法,过多过滤器会增加方法调用层级,需按需开发;
  • 线程安全:过滤器实例为单例,若过滤器中包含成员变量,需保证线程安全;
  • 避免循环拦截:在拦截方法中调用目标对象方法时,避免再次触发自身拦截逻辑。

七、总结

Mybatis过滤器(拦截器)是JDK动态代理与责任链模式的经典应用,其核心设计思路可总结为:

  • 精准拦截:通过@Intercepts+ @Signature注解精准定位需要增强的核心接口和方法;
  • 链式增强:通过InterceptorChain管理多个过滤器,按注册顺序依次执行增强逻辑;
  • 低侵入扩展:无需修改Mybatis源码,通过实现Interceptor接口即可扩展核心功能。

掌握过滤器原理后,不仅能灵活实现分页、字段填充、数据加解密等常见需求,还能基于此定制化扩展Mybatis的核心能力,是Mybatis进阶开发的必备知识点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/993573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

10406_基于Springboot的社交平台系统

1、项目包含 项目源码、项目文档、数据库脚本、软件工具等资料; 带你从零开始部署运行本套系统。 2、项目介绍 本文采用SpringBoot进行设计,采用java技术开发,选用MySQL作为后台数据库。系统针对社交平台的需求,包…

aaaa

"""https://openweathermap.org/apihttps://www.openweathermap.org/current、https://openweathermap.org/current#multi 文档(parameters)unknowspeople1@gmail.com"""import json…

TB331FC原厂刷机包下载_CNZUI_17.0.572_ST_250910

联想平板小新Pad 2024 11英寸 学习办公娱乐影音平板电脑原厂刷机包下载原厂刷机包下载https://pan.quark.cn/s/7cbc148ab4f8 联想平板小新Pad 2024 11英寸 学习办公娱乐影音平板电脑原厂刷机包下载

2025云南短视频制作服务商/公司TOP5推荐!昆明等地短视频制作企业榜单发布,赋能企业品牌传播新生态

随着短视频成为企业品牌营销与用户互动的核心载体,市场对专业短视频制作服务的需求持续攀升。本榜单基于技术创新力、内容创作力、行业适配性及服务效能四大维度,结合主流平台数据反馈与客户案例实证,权威解析2025年…

2025 年 12 月杭州公寓出租权威推荐榜:精选浙江优质房源,温馨宜居与便捷交通的完美之选

2025 年 12 月杭州公寓出租权威推荐榜:精选浙江优质房源,温馨宜居与便捷交通的完美之选 随着城市化进程的深化与新经济业态的蓬勃发展,杭州及整个浙江省的住房租赁市场正经历着一场深刻的品质化与专业化变革。公寓出…

2025年12月北京陪诊公司推荐榜:专业机构对比分析与用户选择指南

在医疗资源高度集中但就医流程日趋复杂的北京,陪诊服务已成为缓解异地就医、老年就诊、行动不便等群体就医困境的重要支撑。据第三方市场调研数据显示,2025年北京陪诊服务需求年均增长率超30%,但市场机构良莠不齐,…

解码继承——代码复用与层次化设计

继承的核心概念 继承的核心是基于已有类(基类)创建新类(派生类),实现代码复用和层次化的类结构设计,让派生类既能复用基类的属性和方法,又能扩展自身特有的功能,体现现实世界中事物的 “一般 - 特殊” 关系(如…

TB365FC刷机包_CN_ZUXOS_1.1.10.122_ST_250828

联想小新平板12.1 2025款12.1英寸2.5K旗舰性能AI平板电脑原厂刷机包下载原厂刷机包下载https://pan.quark.cn/s/0e96a971aa79 联想小新平板12.1 2025款12.1英寸2.5K旗舰性能AI平板电脑原厂刷机包下载

Python 异步编程:使用 async/await 实现高效并发 - 指南

Python 异步编程:使用 async/await 实现高效并发 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas…

超越大语言模型:蒸馏技术实战指南

本文探讨了如何通过模型蒸馏技术将大型语言模型的知识压缩到更小、更快的任务特定组件中,以实现更好的模块化、透明度、数据隐私和成本效益。文章提供了从原型到生产的实用步骤,并通过案例分析展示了蒸馏后模型在精度…

TB520FU刷机包_CN_17.0.10.158_ST_250817

联想YOGA Pad Pro AI元启 12.7英寸 影音办公学习平板电脑原厂刷机包下载原厂刷机包下载https://pan.quark.cn/s/f93e5a5a5f5b联想YOGA Pad Pro AI元启 12.7英寸 影音办公学习平板电脑刷机包

web框架——flask3.x-上下文管理机制

flask3.x——上下文管理机制1. 上下文隔离核心目标:解决多请求并发时的数据混乱问题,让每个请求的相关数据互不干扰,为接收请求时做环境铺垫。实现工具:用contextvars模块里面的contextvar类 解决线程/协程数据隔离…

JavaEE初阶——多线程(9)JUC的程序类和死锁

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

[智能体设计模式] 第 1 章:提示链(Prompt Chaining) - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

极速AI助手 - 多AI服务桌面助手, 支持MCP工具调用, 内置免费AI功能

极速AI助手是一款专业的桌面端多AI服务交互程序。支持接入多种主流AI服务(内置AI、DeepSeek、通义千问等),集成MCP工具调用功能,让AI助手能够执行更多实用任务。支持多对话管理、Markdown渲染、流式响应等功能,是…

蓝鲸花呗客服妙招帮你脱困省油大空间低配拆解银河的“水桶车细节值得吵一架

谁能想到,一台8万出头的B级插混轿车,竟能同时做到续航超2100km、百公里油耗2.67L、轴距2845mm? 吉利银河A7的上市,直接撕掉了“低价必低配”的标签,甚至让合资混动车型的定价逻辑彻底崩塌。有车主实测从北京到武汉一箱…

吴恩达深度学习课程四:计算机视觉 第一周:卷积基础知识(一)图像处理基础

此分类用于记录吴恩达深度学习课程的学习笔记。 课程相关信息链接如下:原课程视频链接:[双语字幕]吴恩达深度学习deeplearning.ai github课程资料,含课件与笔记:吴恩达深度学习教学资料 课程配套练习(中英)与答案…

Python函数基础实战教程:从定义调用到参数传值全解析

一、Python函数核心意义:为什么要学函数基础? Python函数是代码复用、逻辑封装的核心载体,也是新手从「线性代码编写」过渡到「模块化编程」的关键。无论是自动化脚本、数据分析还是Web开发,函数都能让代码更简洁、…

索引数组读取修改添加

索引数组读取修改添加1 $xm = array(小明,男,28,5888.88);2 3 //1.读取数据 4 echo $xm[0].同学的工资是:.$xm[3].元人民币。;5 6 //2.修改数据 7 $xm[0] = 小张;8 $xm[1]…