手写MyBatis第45弹:动态代理在MyBatis插件内核是如何织入扩展逻辑的
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我。
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
目录
博客正文
一、为何是代理模式?
二、JDK动态代理:MyBatis的选择
三、Plugin类:代理机制的枢纽
四、责任链:多层代理的构建
五、总结与思考
Mermaid图:MyBatis多层插件代理责任链调用序列图
-
MyBatis插件内核揭秘:动态代理是如何织入扩展逻辑的?
-
从代理模式入手,彻底理解MyBatis插件的工作机制
-
手写MyBatis插件核心:掌握JDK动态代理与责任链的完美融合
-
MyBatis插件开发精髓:不只是实现Interceptor接口那么简单
-
深入源码:剖析Plugin.wrap()方法如何构建代理责任链
博客正文
在MyBatis的扩展体系中,插件(Plugin)机制无疑是最为强大和精巧的一环。它允许我们无侵入性地拦截并增强MyBatis核心组件的行为。前文我们分析了插件的四大作用点,本文将深入其实现内核,聚焦于支撑这一切的基石——代理模式。我们将探讨MyBatis是如何利用动态代理技术编织出一张灵活的责任链网,从而优雅地实现功能扩展。
一、为何是代理模式?
在软件设计中,我们常常需要为某个对象提供一个代理或占位符,以控制对这个对象的访问。这正是代理模式(Proxy Pattern)的用武之地。MyBatis插件面临的核心问题正是:如何在目标对象的方法执行前后插入自定义逻辑?
最直接但最不优雅的做法是修改MyBatis源码,在Executor#query
等方法内部添加调用插件的代码。但这违反了“开放-封闭原则”,使得扩展和维护变得困难。
代理模式完美地解决了这个问题。它通过创建一个与目标对象实现相同接口的代理对象,由这个代理对象来接管所有对目标对象的调用。在调用转发给真实目标之前或之后,代理对象可以执行额外的操作(如调用插件的intercept
方法)。这对调用者来说是透明的,它无需知道自己是和代理还是真实对象在打交道。
MyBatis插件机制正是这一思想的经典应用:框架在创建Executor
、StatementHandler
等实例时,并非直接返回原始对象,而是返回一个经过层层包装的代理对象。
二、JDK动态代理:MyBatis的选择
Java中实现动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。MyBatis毫不犹豫地选择了前者。这是为什么呢?
因为MyBatis要拦截的四大组件(Executor
, StatementHandler
, ParameterHandler
, ResultSetHandler
)全部都是接口。JDK动态代理恰恰要求目标对象必须实现至少一个接口,它会动态创建一个实现了相同接口的代理类。这完全契合MyBatis的需求。
让我们回顾一下JDK动态代理的两个核心角色:
-
java.lang.reflect.InvocationHandler
:调用处理器接口。它只有一个方法invoke(Object proxy, Method method, Object[] args)
。所有对代理对象的方法调用,都会被路由到这个方法中。 -
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在初始化四大组件时的包装过程如下:
-
框架创建了一个原始的
StatementHandler
对象。 -
框架遍历所有插件,首先轮到
PageInterceptor
。调用Plugin.wrap(originalHandler, pageInterceptor)
。-
PageInterceptor
的签名与StatementHandler
匹配,于是返回一个代理对象Proxy$1
。 -
此时,
Proxy$1
的InvocationHandler
是一个Plugin
实例,它持有originalHandler
和pageInterceptor
。
-
-
接着,框架继续遍历,轮到
MetricsInterceptor
。它拿到的是上一步的结果Proxy$1
,而不是原始对象。它调用Plugin.wrap(proxy$1, metricsInterceptor)
。-
MetricsInterceptor
的签名也与StatementHandler
匹配,于是再次创建代理,返回Proxy$2
。 -
Proxy$2
的InvocationHandler
是另一个Plugin
实例,它持有proxy$1
和metricsInterceptor
。
-
-
最终,框架得到的
StatementHandler
实例是Proxy$2
。
这个包装过程形成了一个“代理套代理”的洋葱结构。当调用Proxy$2
的query
方法时,触发流程如下图所示(请见后面的Mermaid图)。
Invocation.proceed()
方法是推动责任链前进的关键。它在内部使用反射调用了method.invoke(target, args)
。这里的target
是创建Invocation
对象时传入的,即当前代理对象所包裹的那个对象(可能是原始对象,也可能是内层的代理对象)。这个调用会触发内层代理的invoke
方法或最终原始对象的方法,从而让调用得以链式地向内传递。
五、总结与思考
MyBatis的插件机制是代理模式和责任链模式相结合的典范。Plugin
类作为核心枢纽,利用JDK动态代理技术,巧妙地将被拦截的方法调用转发给用户自定义的插件逻辑。通过多层代理的嵌套,构建了一条清晰的责任链,使得多个插件可以有序地协同工作。
理解这一机制,不仅能让我们更好地使用和调试插件,更能让我们在设计自己的系统时,借鉴这种优雅、松耦合的扩展方案,打造出同样强大的可扩展架构。
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!