当前位置: 首页 > news >正文

手写MyBatis第45弹:动态代理在MyBatis插件内核是如何织入扩展逻辑的

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我。

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。 

目录

博客正文

一、为何是代理模式?

二、JDK动态代理:MyBatis的选择

三、Plugin类:代理机制的枢纽

四、责任链:多层代理的构建

五、总结与思考

Mermaid图:MyBatis多层插件代理责任链调用序列图


  1. MyBatis插件内核揭秘:动态代理是如何织入扩展逻辑的?

  2. 从代理模式入手,彻底理解MyBatis插件的工作机制

  3. 手写MyBatis插件核心:掌握JDK动态代理与责任链的完美融合

  4. MyBatis插件开发精髓:不只是实现Interceptor接口那么简单

  5. 深入源码:剖析Plugin.wrap()方法如何构建代理责任链


博客正文

在MyBatis的扩展体系中,插件(Plugin)机制无疑是最为强大和精巧的一环。它允许我们无侵入性地拦截并增强MyBatis核心组件的行为。前文我们分析了插件的四大作用点,本文将深入其实现内核,聚焦于支撑这一切的基石——代理模式。我们将探讨MyBatis是如何利用动态代理技术编织出一张灵活的责任链网,从而优雅地实现功能扩展。

一、为何是代理模式?

在软件设计中,我们常常需要为某个对象提供一个代理或占位符,以控制对这个对象的访问。这正是代理模式(Proxy Pattern)的用武之地。MyBatis插件面临的核心问题正是:如何在目标对象的方法执行前后插入自定义逻辑?

最直接但最不优雅的做法是修改MyBatis源码,在Executor#query等方法内部添加调用插件的代码。但这违反了“开放-封闭原则”,使得扩展和维护变得困难。

代理模式完美地解决了这个问题。它通过创建一个与目标对象实现相同接口的代理对象,由这个代理对象来接管所有对目标对象的调用。在调用转发给真实目标之前或之后,代理对象可以执行额外的操作(如调用插件的intercept方法)。这对调用者来说是透明的,它无需知道自己是和代理还是真实对象在打交道。

MyBatis插件机制正是这一思想的经典应用:框架在创建ExecutorStatementHandler等实例时,并非直接返回原始对象,而是返回一个经过层层包装的代理对象

二、JDK动态代理:MyBatis的选择

Java中实现动态代理主要有两种方式:JDK动态代理CGLIB动态代理。MyBatis毫不犹豫地选择了前者。这是为什么呢?

因为MyBatis要拦截的四大组件(Executor, StatementHandler, ParameterHandler, ResultSetHandler)全部都是接口。JDK动态代理恰恰要求目标对象必须实现至少一个接口,它会动态创建一个实现了相同接口的代理类。这完全契合MyBatis的需求。

让我们回顾一下JDK动态代理的两个核心角色:

  1. java.lang.reflect.InvocationHandler:调用处理器接口。它只有一个方法 invoke(Object proxy, Method method, Object[] args)。所有对代理对象的方法调用,都会被路由到这个方法中。

  2. java.lang.reflect.Proxy:用于动态创建代理类的工具类。

在MyBatis中,这个InvocationHandler的角色就是由org.apache.ibatis.plugin.Plugin类来扮演的。它是连接插件逻辑和代理对象的桥梁。

三、Plugin类:代理机制的枢纽

Plugin类是理解MyBatis插件代理机制的关键。它本身既是一个InvocationHandler,也提供了一系列静态工具方法。

1. Plugin.wrap(Object target, Interceptor interceptor) 方法 这是创建代理对象的入口。它的作用是为目标对象target包装一层代理,但有一个重要前提:只有当插件声明的@Intercepts签名与目标对象的方法匹配时,才会创建代理,否则直接返回原始对象。这是一种优化,避免了不必要的代理层。

其内部逻辑简化如下:

public static Object wrap(Object target, Interceptor interceptor) {// 1. 解析插件上的@Intercepts注解,获取其要拦截的方法签名MapMap<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();// 2. 检查目标对象实现的所有接口中,是否有插件要拦截的接口Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {// 3. 如果存在匹配的接口,则创建代理对象// 注意:这里的InvocationHandler是new Plugin(target, interceptor, signatureMap)return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}// 4. 如果没有接口需要拦截,则直接返回原始对象return target;}

2. Plugin.invoke(Object proxy, Method method, Object[] args) 方法 这是代理对象方法被调用时的核心处理逻辑。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 1. 获取该对象类实现的所有接口的方法集合Set<Method> methods = signatureMap.get(method.getDeclaringClass());// 2. 检查当前被调用的方法是否在插件的拦截范围内if (methods != null && methods.contains(method)) {// 3. 如果在范围内,则调用插件的intercept方法return interceptor.intercept(new Invocation(target, method, args));}// 4. 如果不在拦截范围内,则直接调用原始对象的方法return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}

可以看到,Plugin作为一个聪明的调用处理器,它只对插件声明要拦截的方法才“插手”,将其导向插件的intercept方法。对于其他无关方法,则直接“放行”,调用原始对象的方法,这大大提高了效率。

四、责任链:多层代理的构建

单个插件很简单,但MyBatis允许配置多个插件。如何为同一个组件应用多个插件? 答案就是:嵌套代理,从而形成一条责任链(Chain of Responsibility)

假设我们按顺序配置了分页插件(PageInterceptor)和性能监控插件(MetricsInterceptor)。MyBatis在初始化四大组件时的包装过程如下:

  1. 框架创建了一个原始的StatementHandler对象。

  2. 框架遍历所有插件,首先轮到PageInterceptor。调用Plugin.wrap(originalHandler, pageInterceptor)

    • PageInterceptor的签名与StatementHandler匹配,于是返回一个代理对象Proxy$1

    • 此时,Proxy$1InvocationHandler是一个Plugin实例,它持有originalHandlerpageInterceptor

  3. 接着,框架继续遍历,轮到MetricsInterceptor。它拿到的是上一步的结果Proxy$1,而不是原始对象。它调用Plugin.wrap(proxy$1, metricsInterceptor)

    • MetricsInterceptor的签名也与StatementHandler匹配,于是再次创建代理,返回Proxy$2

    • Proxy$2InvocationHandler是另一个Plugin实例,它持有proxy$1metricsInterceptor

  4. 最终,框架得到的StatementHandler实例是Proxy$2

这个包装过程形成了一个“代理套代理”的洋葱结构。当调用Proxy$2query方法时,触发流程如下图所示(请见后面的Mermaid图)。

Invocation.proceed()方法是推动责任链前进的关键。它在内部使用反射调用了method.invoke(target, args)。这里的target是创建Invocation对象时传入的,即当前代理对象所包裹的那个对象(可能是原始对象,也可能是内层的代理对象)。这个调用会触发内层代理的invoke方法或最终原始对象的方法,从而让调用得以链式地向内传递。

五、总结与思考

MyBatis的插件机制是代理模式和责任链模式相结合的典范。Plugin类作为核心枢纽,利用JDK动态代理技术,巧妙地将被拦截的方法调用转发给用户自定义的插件逻辑。通过多层代理的嵌套,构建了一条清晰的责任链,使得多个插件可以有序地协同工作。

理解这一机制,不仅能让我们更好地使用和调试插件,更能让我们在设计自己的系统时,借鉴这种优雅、松耦合的扩展方案,打造出同样强大的可扩展架构。

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

http://www.dtcms.com/a/360528.html

相关文章:

  • Linux软件升级方法总结
  • CF每日3题(1500-1600)
  • 在windows系统下安装Docker Desktop后迁移镜像位置
  • 科普:requirements.txt 和 environment.yml
  • 【系统分析师】高分论文:论面向服务方法在信息系统开发中的应用
  • 一些八股总结
  • Langflow Agents 技术深度分析
  • OpenCL C 平台与设备
  • (附源码)基于Vue的教师档案管理系统的设计与实现
  • 【开题答辩全过程】以 基于Java的网络购物平台设计与实现为例,包含答辩的问题和答案
  • LeetCode 3665. 统计镜子反射路径数目
  • react-virtualized React 应用中高效渲染大型列表和表格数据的库
  • Synchronized 概述
  • 【LeetCode】18、四数之和
  • LeeCode 37. 解数独
  • 并发编程——10 CyclicBarrier的源码分析
  • Selenium 等待机制:编写稳定可靠的自动化脚本
  • spi总线
  • 7.2elementplus的表单布局与模式
  • MCP SDK 学习二
  • 艾体宝案例 | 数据驱动破局:DOMO 如何重塑宠物零售门店的生存法则
  • Python 2025:AI代理、Rust与异步编程的新时代
  • 张柏芝亮相林家谦演唱会 再次演绎《任何天气》
  • Spring MVC 九大组件源码深度剖析(五):HandlerAdapter - 处理器的执行引擎
  • 三、环境搭建之Docker安装mysql
  • 一、计算机系统知识
  • Springcloud-----Nacos
  • 【influxdb】InfluxDB 2.x 线性写入详解
  • 层次分析法
  • Redis实现短信登录