news 2026/8/11 12:28:49

MyMyBatis ④:手写插件责任链——Interceptor 与 Plugin.wrap

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyMyBatis ④:手写插件责任链——Interceptor 与 Plugin.wrap

系列第 1 篇:手写骨架
系列第 2 篇:ParameterHandler
系列第 3 篇:MappedStatement 与三种 Executor
源码:https://github.com/OrigamiOrigamiOrigami/MyMyBatis

前言

有了Executor之后,横切能力不该继续写进BaseExecutor。官方做法是插件:用 JDK 动态代理把Executor(也可以是 StatementHandler 等)包一层,按签名拦截指定方法。

本篇实现迷你版:Interceptor@Intercepts/@SignaturePlugin.wrapInterceptorChain,并附带一个慢 SQL 示例。


本篇目标

作用
Interceptorintercept/plugin
@Intercepts / @Signature声明拦截哪个接口的哪个方法
PluginInvocationHandler,决定进拦截还是放行
InterceptorChain按注册顺序多层wrap
SlowSqlInterceptor统计query/update耗时

1. 注解怎么声明拦截点

@Intercepts({@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class}),@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})})publicclassSlowSqlInterceptorimplementsInterceptor{...}

type必须是接口。代理只会实现签名里出现过的接口。


2. Plugin.wrap 做了什么

publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);Class<?>[]interfaces=getAllInterfaces(target.getClass(),signatureMap);if(interfaces.length>0){returnProxy.newProxyInstance(target.getClass().getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}

调用时:

Set<Method>methods=signatureMap.get(method.getDeclaringClass());if(methods!=null&&methods.contains(method)){returninterceptor.intercept(newInvocation(target,method,args));}returnmethod.invoke(target,args);

Invocation.proceed()就是method.invoke(target, args),继续往下一层走。


3. 责任链:一层包一层

publicObjectpluginAll(Objecttarget){for(Interceptorinterceptor:interceptors){target=interceptor.plugin(target);}returntarget;}

创建 Executor 时:

Executorexecutor=newSimpleExecutor(...);return(Executor)interceptorChain.pluginAll(executor);

注册插件:

Configuration.builder().database("jdbc.properties").plugin(newSlowSqlInterceptor()).build();

4. 慢 SQL 示例

publicObjectintercept(Invocationinvocation)throwsThrowable{longstart=System.currentTimeMillis();try{returninvocation.proceed();}finally{longcost=System.currentTimeMillis()-start;MappedStatementms=(MappedStatement)invocation.getArgs()[0];if(cost>=thresholdMs){System.out.println("[SlowSQL] "+cost+"ms id="+ms.getId());}else{System.out.println("[SQL] "+cost+"ms id="+ms.getId());}}}

阈值可用setProperties配置。分页插件同理:在query前改BoundSql,这里先不做,避免一篇写爆。


5. 为什么一定要挂在 Executor 上

第 3 篇之前逻辑在 Session 里,横切只能改 Session。收口到 Executor 后:

  • 慢 SQL、审计、租户改写都有统一挂点
  • 换 SIMPLE / BATCH 不影响插件接口
  • 和官方阅读源码时的路径一致

和官方的差距

  1. 官方还能拦StatementHandler/ParameterHandler/ResultSetHandler,我们目前主要示范 Executor
  2. 没有Plugin对内部类、桥接方法的全部边角处理
  3. 分页改写、拦截顺序与@Signature冲突检测未做强校验

总结

  1. 插件 = 带签名的 JDK 代理
  2. 多个插件 = 多层代理
  3. 业务扩展优先写 Interceptor,而不是改 Executor 源码

建议对照:plugin/Plugin.javaSlowSqlInterceptor.javaConfiguration.newExecutor


下篇预告

MyMyBatis ⑤:动态 SQL、TypeHandler 与 XML Mapper。

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

C++ Socket编程实战:从零实现Telnet客户端

1. 项目概述&#xff1a;从Socket到Telnet的实战之路 最近在整理一些网络编程的老项目&#xff0c;发现很多朋友对C Socket编程和实现一个简单的Telnet客户端感到头疼。这其实是一个非常好的练手项目&#xff0c;它能让你一次性把网络通信、字节流处理、协议解析这些核心概念都…

作者头像 李华
网站建设 2026/8/11 12:24:28

Windows下OpenClaw养殖监测系统安装与配置指南

1. Windows环境下OpenClaw安装全指南 在渔业养殖数字化管理领域&#xff0c;OpenClaw作为开源的龙虾养殖监测系统&#xff0c;正逐步改变传统养殖模式。这个基于Python开发的工具包&#xff0c;通过环境传感器数据采集与分析&#xff0c;帮助养殖户实现溶解氧、水温等关键指标的…

作者头像 李华
网站建设 2026/8/11 12:22:59

Linux核心转储调试实战:从GDB技巧到生产环境分析

1. 调试技巧与核心转储分析实战指南作为一名在Linux系统调试领域摸爬滚打多年的老手&#xff0c;我处理过的核心转储文件可以堆满整个硬盘。今天想和大家分享些真正实用的调试技巧&#xff0c;特别是如何从核心转储&#xff08;core dump&#xff09;这个"系统遗书"中…

作者头像 李华
网站建设 2026/8/11 12:22:57

3步轻松下载国家中小学智慧教育平台电子课本:免费PDF获取终极指南

3步轻松下载国家中小学智慧教育平台电子课本&#xff1a;免费PDF获取终极指南 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具&#xff0c;帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载&#xff0c;让您更方便地获取课本内容。…

作者头像 李华