手写MyBatis第47弹:Interceptor接口设计与Invocation上下文传递机制--MyBatis动态代理生成与方法拦截的精妙实现

发布于:2025-09-03 ⋅ 阅读:(15) ⋅ 点赞:(0)

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

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

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

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

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

目录

一、Interceptor接口:插件系统的契约

二、Invocation对象:调用上下文的完整封装

三、proceed()方法:链式调用的引擎

四、Plugin.wrap方法:代理生成的工厂

五、插件如何修改参数和返回值

六、总结:链式调用的设计哲学


  1. MyBatis插件链式调用深度解析:Interceptor接口设计与Invocation上下文传递机制

  2. 手写MyBatis(八):插件链式调用与Invocation.proceed的递归魔法

  3. 从拦截到修改:MyBatis插件如何操纵方法参数与返回值的核心技术

  4. Plugin.wrap方法揭秘:MyBatis动态代理生成与方法拦截的精妙实现


在上一篇文章中,我们探讨了MyBatis多插件管理的责任链模式。今天,我们将深入这个链条的内部,解析链式调用的实现细节,特别是Interceptor接口的设计、Invocation对象的作用以及proceed()方法如何实现递归调用。这些看似简单的组件背后,隐藏着MyBatis插件系统最精妙的设计思想。

一、Interceptor接口:插件系统的契约

Interceptor接口是MyBatis插件系统的核心契约,它定义了插件必须实现的三个关键行为:

 public interface Interceptor {
     // 核心拦截方法:包含插件的主要逻辑
     Object intercept(Invocation invocation) throws Throwable;
     
     // 默认方法:用于包装目标对象生成代理
     default Object plugin(Object target) {
         return Plugin.wrap(target, this);
     }
     
     // 设置插件属性(可选)
     default void setProperties(Properties properties) {
         // 默认空实现
     }
 }

这个接口设计的精妙之处在于:

  1. intercept方法:这是插件的核心,包含了插件的主要业务逻辑。它接收一个Invocation参数,这个参数封装了完整的调用上下文。

  2. plugin默认方法:这是一个非常巧妙的设计。通过提供默认实现,MyBatis让插件开发者无需关心复杂的代理生成逻辑,只需要专注于业务逻辑的实现。这个方法确保了所有插件都使用统一的代理生成机制。

  3. setProperties方法:允许插件接收外部配置参数,增强了插件的灵活性。

二、Invocation对象:调用上下文的完整封装

Invocation对象是插件链式调用的核心载体,它封装了一次方法调用的所有必要信息:

 public class Invocation {
     private final Object target;     // 被代理的原始对象
     private final Method method;     // 被拦截的方法
     private final Object[] args;     // 方法参数
     
     public Invocation(Object target, Method method, Object[] args) {
         this.target = target;
         this.method = method;
         this.args = args;
     }
     
     // 关键方法:继续执行调用链
     public Object proceed() throws InvocationTargetException, IllegalAccessException {
         return method.invoke(target, args);
     }
     
     // Getter方法
     public Object getTarget() { return target; }
     public Method getMethod() { return method; }
     public Object[] getArgs() { return args; }
     
     // 设置参数(用于修改参数)
     public void setArgs(Object[] args) { this.args = args; }
 }

Invocation的设计体现了"信息专家"模式——它将一次方法调用的所有相关信息集中管理,为插件提供了完整的操作上下文。

三、proceed()方法:链式调用的引擎

proceed()方法是整个插件链式调用机制的核心。它的作用看似简单——调用目标方法,但在责任链模式中,它的行为实际上要复杂得多:

从图中可以看出,proceed()方法实际上触发了一个递归的调用过程:

  1. 最外层插件首先执行前置处理逻辑

  2. 调用proceed(),该方法实际上会调用下一个插件intercept方法

  3. 这个过程递归进行,直到最后一个插件调用proceed()

  4. 最后一个proceed()调用原始目标方法

  5. 然后调用栈逐层返回,每个插件执行后置处理逻辑

  6. 最终返回到最外层插件,完成整个调用链

这种设计的美妙之处在于:每个插件都无需知道整个调用链的结构,它只需要调用proceed()并将处理权交给链条的下一个环节即可

四、Plugin.wrap方法:代理生成的工厂

Plugin.wrap()是插件机制中的另一个关键组件,它负责创建动态代理对象:

 public class Plugin implements InvocationHandler {
     private final Object target;                 // 原始对象
     private final Interceptor interceptor;       // 插件实例
     private final Map<Class<?>, Set<Method>> signatureMap; // 方法签名映射
     
     public static Object wrap(Object target, Interceptor interceptor) {
         // 获取插件声明的拦截点
         Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
         Class<?> type = target.getClass();
         
         // 获取目标对象实现的所有需要被拦截的接口
         Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
         
         if (interfaces.length > 0) {
             // 创建动态代理
             return Proxy.newProxyInstance(
                 type.getClassLoader(),
                 interfaces,
                 new Plugin(target, interceptor, signatureMap));
         }
         
         return target;
     }
     
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         // 检查当前方法是否需要被拦截
         Set<Method> methods = signatureMap.get(method.getDeclaringClass());
         if (methods != null && methods.contains(method)) {
             // 如果需要拦截,调用插件的intercept方法
             return interceptor.intercept(new Invocation(target, method, args));
         }
         // 否则直接调用原始方法
         return method.invoke(target, args);
     }
 }

Plugin.wrap()的智能之处在于它只会为那些实现了插件声明要拦截的接口的对象创建代理,避免了不必要的性能开销。

五、插件如何修改参数和返回值

基于上述架构,插件可以很容易地修改方法参数和返回值:

1. 修改方法参数:

 @Override
 public Object intercept(Invocation invocation) throws Throwable {
     // 获取原始参数
     Object[] args = invocation.getArgs();
     
     // 修改参数(例如加密参数)
     if (args[0] instanceof String) {
         args[0] = encrypt((String) args[0]);
     }
     
     // 重要:将修改后的参数设置回Invocation
     invocation.setArgs(args);
     
     // 继续执行调用链
     return invocation.proceed();
 }

2. 修改返回值:

 @Override
 public Object intercept(Invocation invocation) throws Throwable {
     // 执行原有逻辑并获取返回值
     Object result = invocation.proceed();
     
     // 修改返回值(例如解密结果)
     if (result instanceof String) {
         result = decrypt((String) result);
     }
     
     return result;
 }

3. 完全替换方法逻辑:

 @Override
 public Object intercept(Invocation invocation) throws Throwable {
     // 不调用proceed,完全由插件实现方法逻辑
     if (shouldReplaceLogic(invocation)) {
         return customLogic(invocation);
     }
     
     // 否则正常执行原有逻辑
     return invocation.proceed();
 }
六、总结:链式调用的设计哲学

MyBatis插件链式调用机制的设计体现了多个重要的软件设计原则:

  1. 开闭原则:通过插件机制,可以在不修改框架源码的情况下扩展功能。

  2. 单一职责原则:每个插件只关注一个特定的功能点。

  3. 依赖倒置原则:插件依赖于抽象的Interceptor接口,而不是具体的实现。

  4. 信息专家模式Invocation对象集中管理了调用相关的所有信息。

这种设计不仅使得MyBatis插件系统极其强大和灵活,也为我们提供了如何设计可扩展架构的宝贵范例。无论是开发框架还是业务系统,这种责任链模式和链式调用的思想都值得深入学习和应用。


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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到