5.3 插件机制:四大拦截点


文档摘要

5.3 插件机制:四大拦截点 本节摘要:插件是插在执行链上的"编外器械"——框架在创建四大核心对象时用动态代理包一层,你声明要拦哪个对象的哪个方法,调用就会先经过你的代码。本节先认清四大对象各自能拦什么,再拆开 Plugin.wrap 的责任链看原理,最后手写一个慢 SQL 计时插件并注册生效。 5.2 的 TypeHandler 管"点"上的翻译——每个字段一对类型。这一节的插件管"链"上的插队——横跨所有 SQL 的共性需求,分页、慢 SQL 审计、数据脱敏,都不必改业务代码。 四大对象,各管一段 SQL 从提交到返回,要经过四个核心对象的手。

5.3 插件机制:四大拦截点

本节摘要:插件是插在执行链上的"编外器械"——框架在创建四大核心对象时用动态代理包一层,你声明要拦哪个对象的哪个方法,调用就会先经过你的代码。本节先认清四大对象各自能拦什么,再拆开 Plugin.wrap 的责任链看原理,最后手写一个慢 SQL 计时插件并注册生效。

5.2 的 TypeHandler 管"点"上的翻译——每个字段一对类型。这一节的插件管"链"上的插队——横跨所有 SQL 的共性需求,分页、慢 SQL 审计、数据脱敏,都不必改业务代码。

四大对象,各管一段

SQL 从提交到返回,要经过四个核心对象的手。每个对象都留了可拦的方法口:

对象 职责 常拦的方法
Executor 调度器:管增删改查的执行、事务提交回滚 query、update、commit、rollback
StatementHandler 语句处理器:管 PreparedStatement 创建与执行 prepare、parameterize、query
ParameterHandler 参数处理器:管参数设值 setParameter
ResultSetHandler 结果处理器:管结果集到对象的映射 handleResultSets

选哪个口下刀,取决于你想改什么:改 SQL 文本(加 LIMIT、加权限过滤)拦 StatementHandler.prepare;只记录执行耗时拦 Executor.query/update;做参数加解密拦 ParameterHandler;做结果脱敏拦 ResultSetHandler。

原理:一层代理的责任链

框架在创建四大对象时检查插件清单,有的话就用 Plugin.wrap 把对象包成 JDK 动态代理。多个插件按配置顺序层层包裹,调用时像洋葱一样一层层进出:

插件责任链:调用像洋葱一样一层层进出

看图里最重要的一件事:每个插件的 intercept 方法拿到 Invocation 后,必须调 invocation.proceed() 让调用继续往下走。proceed 之后的结果还可以再加工——这就是"返回原路再加工"的落点。

手写一个:慢 SQL 计时插件

背景:想在不改任何业务代码的前提下,把执行超过 500 毫秒的 SQL 连同耗时打出来告警。拦 Executor 的 query 和 update 两个口就够覆盖全量读写。

@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class SlowSqlTimerPlugin implements Interceptor { private long thresholdMs = 500; // 慢 SQL 阈值,可由 properties 注入 @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; long start = System.currentTimeMillis(); try { return invocation.proceed(); // 放行:继续走真实执行 } finally { long cost = System.currentTimeMillis() - start; if (cost >= thresholdMs) { System.out.printf("[慢SQL] %dms -> %s (%s)%n", cost, ms.getId(), invocation.getMethod().getName()); // 生产环境这里接告警通道,而不是控制台 } } } @Override public Object plugin(Object target) { // 只对四大接口对象包代理,其他对象原样放行 return (target instanceof Executor) ? Plugin.wrap(target, this) : target; } @Override public void setProperties(Properties props) { String v = props.getProperty("thresholdMs"); if (v != null) thresholdMs = Long.parseLong(v); } }

三个方法各司其职:@Intercepts/@Signature 声明要拦的口(接口 + 方法名 + 参数类型,三者必须完全对得上,参数列表写错一个就拦不到);intercept 是核心——记录开始时间,proceed 放行,finally 里算耗时,保证异常路径也能记账;plugin 决定包不包

注册——配置文件里一行声明,属性顺带注入:

<plugins> <plugin interceptor="com.example.plugin.SlowSqlTimerPlugin"> <property name="thresholdMs" value="500"/> </plugin> </plugins>

验证:造一条必然慢的查询(比如 SQL 里塞 SLEEP(1)),执行一次:

[慢SQL] 1013ms -> com.example.mapper.UserMapper.findByComplexCond (query)

mapper id 和耗时都有了,定位到方法级。

⚠️ 常见坑:@Signature 的 args 必须与方法签名的参数类型逐位对应。Executor.query 有一个带 ResultHandler 的五参重载、一个带 RowBounds 的四参重载,只写四参就拦不到传了 ResultHandler 的那次调用。拿不准时把两个重载都声明上。

💡 关键直觉:插件是全局横切工具,代价是每一次 SQL 调用都多穿一层代理。能拦一个方法就别拦整个接口,能拦 Executor 就别深入 StatementHandler——拦截点越深,改坏执行链的风险越大。分页、审计这类框架级需求才用插件,单次业务差异请走 SQL 参数。

本节要点回顾

  • 四大对象四个口:Executor 管调度、StatementHandler 管语句、ParameterHandler 管参数、ResultSetHandler 管结果,改什么拦什么
  • 一层代理一层皮:Plugin.wrap 用 JDK 动态代理层层包裹,配置顺序决定包裹顺序
  • proceed() 不能省:忘了放行等于掐断调用链,SQL 根本不会执行
  • @Signature 要精确:接口、方法名、参数类型逐位对上才拦得到,重载方法要分开声明
  • 插件求窄不求宽:拦截点越深风险越大,全局横切需求才值得动用插件

编外器械装完了,下一节上最后一件监护设备:MBG——三十张表的标准手术方案,让代码生成器批量预制。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U