手写MyBatis第46弹:多插件责任链模式的实现原理与执行顺序奥秘--MyBatis插件架构深度解析

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

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

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

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

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


  1. MyBatis插件架构深度解析:多插件责任链模式的实现原理与执行顺序奥秘

  2. 手写MyBatis(七):InterceptorChain设计与多插件嵌套代理的实现策略

  3. 从单插件到多插件:MyBatis插件系统的架构演进与责任链模式实践

  4. MyBatis插件执行顺序揭秘:为什么后配置的插件先执行?


在前一篇文章中,我们探讨了MyBatis插件的基本原理和单个插件的实现方式。然而,在实际的企业级应用中,我们往往需要同时使用多个插件来实现不同的功能,比如同时使用分页插件、性能监控插件和数据加密插件。今天,我们将深入探讨MyBatis如何通过精巧的责任链模式设计,实现对多个插件的协同管理,并解析其执行顺序的内在逻辑。

一、多插件管理的挑战:为何需要InterceptorChain?

单个插件的实现相对简单,但当多个插件同时存在时,就会面临一系列复杂的问题:

  1. 执行顺序问题:多个插件应该按照什么顺序执行?哪个插件的逻辑先处理,哪个后处理?

  2. 代理嵌套问题:如何确保每个插件都能正确拦截目标方法,而不会相互干扰?

  3. 统一管理问题:如何集中管理所有插件,避免散落在代码的各个角落?

MyBatis的解决方案是引入一个专门的管理器------InterceptorChain(拦截器链)。这个类的设计体现了经典的责任链模式(Chain of Responsibility Pattern),它将所有插件组织成一条链,让每个插件都有机会处理请求。

二、InterceptorChain的核心设计与实现

InterceptorChain的核心职责非常明确:管理和执行所有插件。它的实现通常包含以下关键部分:

java 复制代码
 public class InterceptorChain {
     // 存储所有拦截器的列表
     private final List<Interceptor> interceptors = new ArrayList<>();
     
     // 添加插件到链中
     public void addInterceptor(Interceptor interceptor) {
         interceptors.add(interceptor);
     }
     
     // 对目标对象应用所有插件
     public Object pluginAll(Object target) {
         // 遍历所有插件
         for (Interceptor interceptor : interceptors) {
             // 检查插件是否声明要拦截此类对象
             if (interceptor instanceof Interceptor) {
                 // 使用插件包装目标对象
                 target = interceptor.plugin(target);
             }
         }
         return target;
     }
     
     // 获取所有插件
     public List<Interceptor> getInterceptors() {
         return Collections.unmodifiableList(interceptors);
     }
 }

这个看似简单的类,却是整个多插件系统的核心。它的pluginAll方法实现了插件的嵌套代理机制。

三、多插件的工作流程:层层代理的魔法

多插件的工作流程可以分为配置阶段和运行时阶段:

1. 配置阶段:插件注册与链构建

在MyBatis初始化时,所有通过配置文件或注解声明的插件都会被添加到InterceptorChain中:

java 复制代码
 // 在配置类中
 InterceptorChain chain = new InterceptorChain();
 chain.addInterceptor(new PaginationInterceptor());  // 分页插件
 chain.addInterceptor(new PerformanceInterceptor()); // 性能监控插件
 chain.addInterceptor(new EncryptionInterceptor());  // 数据加密插件

2. 运行时阶段:四大组件的代理包装

当需要创建ExecutorStatementHandlerParameterHandlerResultSetHandler时,框架会调用InterceptorChain.pluginAll()方法:

java 复制代码
 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     // 1. 创建原始的目标对象
     Executor executor = new SimpleExecutor(transaction);
     
     // 2. 应用所有插件
     executor = (Executor) interceptorChain.pluginAll(executor);
     
     return executor;
 }
四、插件执行顺序的奥秘:后配置先执行

这是一个非常关键且容易混淆的概念:InterceptorChain中,后添加的插件会先执行。这是因为插件是通过嵌套代理的方式实现的,后添加的插件位于代理链的外层。

让我们通过一个具体的例子来理解这个过程:

java 复制代码
 // 假设我们按顺序添加三个插件
 chain.addInterceptor(plugin1); // 最先添加
 chain.addInterceptor(plugin2); // 其次添加  
 chain.addInterceptor(plugin3); // 最后添加
 ​
 // 当调用pluginAll时,实际的包装过程是:
 Object target = new SimpleExecutor(); // 原始对象
 target = plugin3.plugin(target);     // 最外层代理
 target = plugin2.plugin(target);     // 中间层代理
 target = plugin1.plugin(target);     // 最内层代理

最终的代理结构如下图所示:

从图中可以清晰看出,虽然plugin1最先被添加到链中,但它实际上是最内层的代理,因此最后执行。而plugin3最后被添加,却成为最外层的代理,最先执行。

这种"后进先出"的执行顺序有着重要的实际意义:后添加的插件可以对先添加的插件的处理结果进行再处理。例如,如果先添加数据加密插件,后添加SQL日志插件,那么日志插件记录的是加密后的SQL,这可能不是我们想要的。通过调整添加顺序,我们可以让日志插件先记录原始SQL,然后再由加密插件进行处理。

五、Plugin.wrap方法:代理机制的实现核心

在每个插件的plugin方法中,通常会调用一个统一的工具方法Plugin.wrap()

java 复制代码
 public class MyPlugin implements Interceptor {
     @Override
     public Object plugin(Object target) {
         // 使用Plugin工具类创建代理
         return Plugin.wrap(target, this);
     }
     
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
         // 插件的具体逻辑
         return invocation.proceed();
     }
 }

Plugin.wrap()方法的内部实现是理解整个插件机制的关键:

java 复制代码
 public class Plugin implements InvocationHandler {
     public static Object wrap(Object target, Interceptor interceptor) {
         // 1. 获取插件声明的要拦截的接口和方法
         Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
         
         // 2. 检查目标对象是否实现了插件要拦截的接口
         Class<?> type = target.getClass();
         Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
         
         if (interfaces.length > 0) {
             // 3. 创建动态代理对象
             return Proxy.newProxyInstance(
                 type.getClassLoader(),
                 interfaces,
                 new Plugin(target, interceptor, signatureMap));
         }
         
         return target;
     }
 }

这个方法的核心作用是:只为那些实现了插件声明要拦截的接口的目标对象创建代理。这样就避免了不必要的代理开销,也确保了插件只会拦截它真正关心的方法。

六、总结:责任链模式的价值

MyBatis的多插件机制是一个经典的责任链模式应用,它的价值在于:

  1. 解耦:每个插件只关注自己的功能,不需要知道其他插件的存在。

  2. 灵活扩展:可以轻松地添加、移除或调整插件,而不需要修改框架核心代码。

  3. 执行顺序可控:通过调整插件的添加顺序,可以控制插件的执行顺序。

  4. 职责单一:每个插件都有明确的职责范围,符合单一职责原则。

通过这种精巧的设计,MyBatis提供了一个极其强大且灵活的扩展机制,使得开发者可以在不修改框架源码的情况下,深度定制和增强框架的功能。这种设计思路值得我们深入学习和借鉴,无论是在框架设计还是日常业务开发中,责任链模式都是一种非常有价值的架构模式。


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

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

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

相关推荐
To_OC9 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC9 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志10 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长11 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯11 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能