news 2026/5/14 2:40:54

设计模式:责任链模式(MyBatis)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式:责任链模式(MyBatis)

目录

一、核心角色对应(责任链模式)

二、MyBatis 责任链的核心拦截点

三、责任链执行流程(以 StatementHandler 为例)

1. 整体流程

2. 关键环节:嵌套代理构建责任链

四、代码示例:自定义插件实现责任链

步骤 1:定义抽象处理者(复用 MyBatis 原生 Interceptor)

步骤 2:实现具体处理者(自定义插件)

插件 1:SQL 日志插件(记录最终执行的 SQL)

插件 2:分页插件(改写 SQL 拼接分页条件)

步骤 3:配置插件(构建责任链)

步骤 4:执行效果(责任链执行流程)

五、责任链核心实现:Plugin 类(管理器)

关键逻辑:

六、MyBatis 责任链的核心特点

1. 嵌套代理式责任链(区别于列表式)

2. 不可中断的纯责任链

3. 精准拦截(按方法签名匹配)

4. 无侵入扩展

七、典型应用场景

八、使用注意事项

总结


MyBatis 基于责任链模式 + JDK 动态代理实现了插件(Interceptor)机制,核心目标是对 SQL 执行全流程(参数处理、SQL 构建、结果集返回等)进行无侵入的拦截增强,多个插件可按顺序形成责任链,依次处理目标对象,最终执行原生逻辑。

与 Spring MVC/Security 的 “列表式责任链” 不同,MyBatis 责任链是嵌套代理式:每个插件通过动态代理包裹目标对象,请求沿代理链传递,本质仍是 “请求沿链传递、处理者自主决策” 的责任链核心思想。

一、核心角色对应(责任链模式)

责任链模式角色MyBatis 对应实现核心职责
抽象处理者(Handler)org.apache.ibatis.plugin.Interceptor接口定义拦截核心方法(intercept)、代理创建方法(plugin),是所有插件的统一接口
具体处理者自定义插件(如分页插件 PageHelper、SQL 日志插件)、MyBatis 内置插件实现Interceptor,指定拦截点(如StatementHandlerprepare方法),处理拦截逻辑后传递请求
责任链管理器org.apache.ibatis.plugin.Plugin基于 JDK 动态代理构建插件链,触发拦截方法执行,控制请求传递
被处理的 “请求” 对象MyBatis 核心执行组件(Executor/StatementHandler/ParameterHandler/ResultSetHandler插件拦截的目标,也是请求传递的载体(如修改StatementHandler的 SQL 内容)
最终处理者MyBatis 原生组件的方法(如StatementHandler.prepare()责任链执行完毕后,执行原生 SQL 处理逻辑

二、MyBatis 责任链的核心拦截点

MyBatis 插件可拦截 4 个核心组件的方法(即责任链可处理的 “请求类型”),覆盖 SQL 执行全流程:

拦截组件拦截时机典型用途
ExecutorSQL 执行前 / 后(query/update分页、缓存增强、SQL 耗时统计
StatementHandlerSQL 构建 / 执行前(prepare/parameterizeSQL 改写、动态拼接条件
ParameterHandlerSQL 参数绑定前(setParameters参数加密、参数格式化
ResultSetHandler结果集处理后(handleResultSets结果集脱敏、数据类型转换

三、责任链执行流程(以StatementHandler为例)

1. 整体流程

plaintext

MyBatis 执行 SQL → 创建 `StatementHandler` 对象 → Plugin 类为其生成代理对象(嵌套所有插件)→ 调用 `StatementHandler.prepare()` → 触发代理链执行: 插件1.intercept() → 处理逻辑(如改写 SQL)→ 调用 `invocation.proceed()` → 插件2.intercept() → 处理逻辑(如记录 SQL)→ 调用 `invocation.proceed()` → ... → 执行原生 `StatementHandler.prepare()` → 执行 SQL 并返回结果

2. 关键环节:嵌套代理构建责任链

MyBatis 插件链通过Plugin.wrap()方法嵌套构建,例如配置了 “分页插件 + 日志插件”:

plaintext

原始 StatementHandler → 被日志插件代理(LogPlugin)→ 被分页插件代理(PagePlugin)→ 最终代理对象

调用代理对象的prepare()时,执行顺序为:PagePlugin.intercept()LogPlugin.intercept()→ 原始prepare(),即配置顺序 = 代理外层顺序 = 拦截执行顺序

四、代码示例:自定义插件实现责任链

步骤 1:定义抽象处理者(复用 MyBatis 原生 Interceptor)

MyBatis 已提供抽象处理者接口,无需自定义:

java

运行

public interface Interceptor { // 核心拦截方法:处理逻辑,通过 invocation.proceed() 传递请求 Object intercept(Invocation invocation) throws Throwable; // 创建代理对象:将当前插件加入责任链 default Object plugin(Object target) { return Plugin.wrap(target, this); } // 设置插件属性(从 mybatis-config.xml 读取) default void setProperties(Properties properties) {} }

步骤 2:实现具体处理者(自定义插件)

插件 1:SQL 日志插件(记录最终执行的 SQL)

java

运行

@Intercepts({ // 指定拦截点:拦截 StatementHandler 的 prepare 方法 @Signature( type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class} ) }) public class SqlLogInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 1. 获取目标对象(StatementHandler) StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); // 2. 反射获取 SQL 内容(MyBatis 提供的反射工具) MetaObject metaObject = SystemMetaObject.forObject(statementHandler); String sql = (String) metaObject.getValue("delegate.boundSql.sql"); // 3. 处理逻辑:打印 SQL System.out.println("【SQL 日志插件】执行 SQL:" + sql); // 4. 传递请求:执行下一个插件/原生方法 return invocation.proceed(); } }
插件 2:分页插件(改写 SQL 拼接分页条件)

java

运行

@Intercepts({ @Signature( type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class} ) }) public class PageInterceptor implements Interceptor { private int pageSize; // 每页条数 @Override public Object intercept(Invocation invocation) throws Throwable { // 1. 获取目标对象 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); // 2. 处理逻辑:改写 SQL(拼接 LIMIT) String originalSql = (String) metaObject.getValue("delegate.boundSql.sql"); String pageSql = originalSql + " LIMIT " + pageSize; metaObject.setValue("delegate.boundSql.sql", pageSql); System.out.println("【分页插件】改写 SQL:" + pageSql); // 3. 传递请求 return invocation.proceed(); } @Override public void setProperties(Properties properties) { // 读取配置的分页大小 pageSize = Integer.parseInt(properties.getProperty("pageSize", "10")); } }

步骤 3:配置插件(构建责任链)

mybatis-config.xml中注册插件,配置顺序即责任链执行顺序

xml

<configuration> <plugins> <!-- 第一个注册:分页插件(外层代理) --> <plugin interceptor="com.example.PageInterceptor"> <property name="pageSize" value="5"/> </plugin> <!-- 第二个注册:日志插件(内层代理) --> <plugin interceptor="com.example.SqlLogInterceptor"/> </plugins> </configuration>

步骤 4:执行效果(责任链执行流程)

当执行userMapper.selectAll()时,输出如下:

plaintext

【分页插件】改写 SQL:SELECT * FROM user LIMIT 5 【SQL 日志插件】执行 SQL:SELECT * FROM user LIMIT 5
  • 执行顺序:分页插件先拦截(修改 SQL)→ 日志插件后拦截(打印最终 SQL)→ 执行原生prepare()方法;
  • 核心传递逻辑:每个插件通过invocation.proceed()触发下一个插件 / 原生方法执行,实现 “请求沿链传递”。

五、责任链核心实现:Plugin 类(管理器)

MyBatis 的Plugin类是责任链的核心,基于 JDK 动态代理实现拦截和传递,核心源码简化如下:

java

运行

public class Plugin implements InvocationHandler { private final Object target; // 被代理的目标对象(如 StatementHandler) private final Interceptor interceptor; // 当前插件 private final Map<Class<?>, Set<Method>> signatureMap; // 插件要拦截的方法 // 创建代理对象(嵌套构建责任链) public static Object wrap(Object target, Interceptor interceptor) { // 获取插件要拦截的方法映射 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); // 获取目标类的所有接口(JDK 动态代理需要接口) Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // 生成代理对象:当前 Plugin 作为 InvocationHandler return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap) ); } return target; // 无需拦截,返回原对象 } // 代理对象调用方法时触发(核心:责任链执行) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 判断当前方法是否是插件要拦截的方法 if (signatureMap.containsKey(method.getDeclaringClass())) { if (signatureMap.get(method.getDeclaringClass()).contains(method)) { // 执行插件的 intercept 方法,传入 Invocation(封装目标、方法、参数) return interceptor.intercept(new Invocation(target, method, args)); } } // 不拦截,直接执行目标方法(传递请求) return method.invoke(target, args); } }

关键逻辑:

  1. wrap()方法:为目标对象生成代理,若有多个插件,会嵌套生成代理(如Plugin.wrap(Plugin.wrap(target, pagePlugin), logPlugin));
  2. invoke()方法:代理对象调用方法时,先执行插件的intercept方法,再通过Invocation.proceed()触发下一层代理 / 原生方法。

六、MyBatis 责任链的核心特点

1. 嵌套代理式责任链(区别于列表式)

  • 传统责任链(如 Spring MVC):用列表存储处理者,循环执行;
  • MyBatis 责任链:用动态代理嵌套处理者,请求沿代理链传递,更轻量且适配 “拦截方法执行” 的场景。

2. 不可中断的纯责任链

MyBatis 插件必须通过invocation.proceed()传递请求(否则原生逻辑无法执行),无法像 Spring MVC 那样 “中断链”,属于纯责任链(请求必须传递到最终处理者)。

3. 精准拦截(按方法签名匹配)

通过@Intercepts+@Signature注解指定拦截的 “组件 + 方法 + 参数”,仅拦截匹配的方法,避免无效处理。

4. 无侵入扩展

新增插件只需实现Interceptor并配置,无需修改 MyBatis 源码,符合 “开闭原则”。

七、典型应用场景

  1. 分页插件(PageHelper):拦截Executorquery方法或StatementHandlerprepare方法,拼接分页 SQL(如LIMIT/ROWNUM);
  2. SQL 监控 / 日志:拦截StatementHandlerprepare方法,记录 SQL 内容、执行耗时、参数信息;
  3. 数据脱敏:拦截ParameterHandler(入参脱敏,如手机号隐藏中间 4 位)、ResultSetHandler(出参脱敏);
  4. 多租户适配:拦截StatementHandler,自动为 SQL 拼接租户 ID 条件(如WHERE tenant_id = ?);
  5. 参数加密 / 解密:拦截ParameterHandler对敏感参数(如密码)加密,拦截ResultSetHandler对结果解密。

八、使用注意事项

  1. 插件顺序:配置顺序决定执行顺序(外层插件先执行),需注意依赖(如分页插件应在日志插件前执行,否则日志打印原 SQL);
  2. 反射安全:修改 MyBatis 内部属性(如 SQL)时,必须使用MetaObject(MyBatis 原生反射工具),避免直接反射导致版本兼容问题;
  3. 性能控制:插件会增加 SQL 执行耗时,避免过多 / 复杂插件(如同时拦截ExecutorStatementHandler);
  4. 方法匹配@Signatureargs必须与目标方法参数完全一致(如StatementHandler.prepare()的参数是ConnectionInteger,需精准匹配)。

总结

MyBatis 是责任链模式在 “ORM 框架拦截 SQL 执行” 场景的经典落地:

  1. 核心载体Interceptor是抽象处理者,Plugin是责任链管理器,通过动态代理嵌套构建插件链;
  2. 执行逻辑:请求沿代理链传递,每个插件处理后通过invocation.proceed()传递给下一个插件,最终执行原生逻辑;
  3. 核心价值:无侵入扩展 SQL 执行流程,适配分页、日志、脱敏等多样化业务需求;
  4. 关键扩展:自定义插件只需实现Interceptor,配置后即可加入责任链,是 MyBatis 灵活性的核心体现。

掌握这一机制,是开发 MyBatis 定制化插件(如自定义分页、SQL 改写)的核心前提。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/14 6:18:33

网络安全求职秘籍:从漏洞挖掘到应急响应,新手到大神的通关手册

【收藏必备】网络安全面试宝典&#xff1a;从OWASP到内网渗透&#xff0c;小白到专家的进阶指南 本文全面整理网络安全面试题&#xff0c;涵盖HVV、OWASP Top 10漏洞原理与修复方法。详细讲解内网渗透技术、权限维持方法、Windows/Linux系统提权技巧&#xff0c;以及渗透测试流…

作者头像 李华
网站建设 2026/5/14 3:35:13

SSLPinDetect:面向Android安全分析的先进SSL Pinning检测工具

在移动应用安全不断演变的格局中&#xff0c;SSL Pinning已成为抵御中间人攻击的关键防御机制。然而&#xff0c;对于安全研究人员和渗透测试人员来说&#xff0c;识别这些实现可能是一项耗时且复杂的任务。 我为什么要创建 SSLPinDetect 我创建此工具的目的是为了在扫描应用中…

作者头像 李华
网站建设 2026/5/14 3:35:13

Open-AutoGLM部署避坑指南:3大关键步骤决定你能否成功运行

第一章&#xff1a;Open-AutoGLM部署前的环境准备与认知 在部署 Open-AutoGLM 之前&#xff0c;充分理解其运行依赖和系统要求是确保后续流程顺利的关键。该框架基于 PyTorch 构建&#xff0c;依赖 CUDA 加速进行高效推理&#xff0c;因此需提前配置兼容的硬件与软件环境。 系…

作者头像 李华
网站建设 2026/5/12 6:38:07

做1688批发系统5年,被商品详情API坑到连夜改代码的实战手记

在电商开发圈混了快十年&#xff0c;1688的商品详情API绝对是最“特立独行”的存在。作为批发平台&#xff0c;它的接口返回里藏着太多零售平台没有的“暗门”——从阶梯价的诡异格式到混批规则的嵌套逻辑&#xff0c;每次对接都像拆盲盒。今天就把这些年踩过的坑、攒的实战代码…

作者头像 李华
网站建设 2026/5/4 23:07:59

结合Jupyter Notebook实现数据科学问答分析

结合 Jupyter Notebook 实现数据科学问答分析 在企业数据分析实践中&#xff0c;一个老生常谈的问题是&#xff1a;业务人员有明确的分析需求&#xff0c;却无法直接操作数据&#xff1b;而数据团队忙于响应各类临时查询&#xff0c;疲于奔命。更糟糕的是&#xff0c;每次分析结…

作者头像 李华