手写MyBatis第56弹:动态代理与责任链的完美结合MyBatis插件架构

#王者杯·14天创作挑战营·第5期#

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

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

🔥🔥🔥(源码 + 调试运行 + 问题答疑)🔥🔥🔥  有兴趣可以联系我

🔥🔥🔥  文末有往期免费源码,直接领取获取(无删减,无套路)

  1. MyBatis插件机制深度解析:从原理到实战应用

  2. 手写实现MyBatis插件系统:揭秘拦截器背后的设计哲学

  3. MyBatis插件开发全攻略:原理剖析与最佳实践

  4. 深入理解MyBatis插件架构:动态代理与责任链的完美结合

  5. MyBatis拦截器机制详解:如何优雅扩展框架功能

正文

MyBatis作为一款优秀的持久层框架,其强大的插件机制为开发者提供了极大的灵活性。插件机制允许我们在不修改框架源码的情况下,对MyBatis的核心组件进行功能扩展和增强。本文将深入剖析MyBatis插件机制的核心原理、实现细节以及实际应用场景。

一、MyBatis插件机制概述

MyBatis插件机制基于动态代理和责任链模式实现,通过拦截器(Interceptor)对四大核心组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)的方法进行拦截和增强。这种设计使得开发者可以非侵入式地扩展MyBatis功能,实现了开闭原则的良好实践。

二、核心组件详解

1. Interceptor接口

Interceptor是插件机制的核心接口,所有自定义插件都必须实现此接口。它定义了三个方法:

 public interface Interceptor {
   Object intercept(Invocation invocation) throws Throwable;
   Object plugin(Object target);
   void setProperties(Properties properties);
 }
  • intercept:执行拦截逻辑的核心方法

  • plugin:用于包装目标对象,返回代理对象

  • setProperties:设置插件属性

2. @Intercepts和@Signature注解

这两个注解用于定义插件的拦截规则:

 @Intercepts({
   @Signature(
     type = StatementHandler.class,
     method = "prepare",
     args = {Connection.class, Integer.class}
   )
 })
  • @Intercepts:标识该类是一个拦截器,包含多个@Signature

  • @Signature:定义具体的拦截点,包括拦截的类、方法和方法参数

3. InterceptorChain责任链

InterceptorChain维护了所有已配置的拦截器,负责按顺序执行这些拦截器:

public class InterceptorChain {
   private final List<Interceptor> interceptors = new ArrayList<>();
   
   public Object pluginAll(Object target) {
     for (Interceptor interceptor : interceptors) {
       target = interceptor.plugin(target);
     }
     return target;
   }
 }
4. Plugin代理包装工具

Plugin类是MyBatis提供的工具类,基于JDK动态代理实现,用于创建代理对象:

 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) {
     // 创建代理对象逻辑
   }
 }
5. Invocation调用上下文

Invocation封装了被代理方法的调用信息,提供了 proceed() 方法用于执行原始方法:

 public class Invocation {
   private final Object target;
   private final Method method;
   private final Object[] args;
   
   public Object proceed() throws InvocationTargetException, IllegalAccessException {
     return method.invoke(target, args);
   }
 }

三、插件生效流程

MyBatis插件的生效过程可以分为以下几个步骤:

  1. 配置解析阶段:MyBatis解析配置文件中的<plugins>标签,实例化拦截器并设置属性

  2. 拦截器注册:将拦截器添加到InterceptorChain中

  3. 对象包装阶段:在创建四大核心组件时,调用InterceptorChain.pluginAll()方法

  4. 代理调用阶段:当调用被代理对象的方法时,触发Plugin.invoke()方法

  5. 精确拦截判断:检查当前方法是否匹配@Signature定义的规则

  6. 责任链执行:按顺序执行所有匹配的拦截器,最后执行原始方法

四、插件开发实践

1. 性能监控插件示例

下面是一个简单的SQL执行时间监控插件:

 @Intercepts({
   @Signature(
     type = Executor.class,
     method = "update",
     args = {MappedStatement.class, Object.class}
   ),
   @Signature(
     type = Executor.class,
     method = "query",
     args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
   )
 })
 public class PerformanceInterceptor implements Interceptor {
   private static final Logger logger = LoggerFactory.getLogger(PerformanceInterceptor.class);
   private long threshold = 1000; // 默认阈值1秒
   
   @Override
   public Object intercept(Invocation invocation) throws Throwable {
     long start = System.currentTimeMillis();
     Object result = invocation.proceed();
     long end = System.currentTimeMillis();
     long time = end - start;
     
     if (time > threshold) {
       Object[] args = invocation.getArgs();
       MappedStatement ms = (MappedStatement) args[0];
       String methodName = ms.getId();
       logger.warn("方法 {} 执行耗时: {} ms", methodName, time);
     }
     
     return result;
   }
   
   @Override
   public Object plugin(Object target) {
     return Plugin.wrap(target, this);
   }
   
   @Override
   public void setProperties(Properties properties) {
     String thresholdStr = properties.getProperty("threshold");
     if (thresholdStr != null) {
       this.threshold = Long.parseLong(thresholdStr);
     }
   }
 }
2. 分页插件原理

分页插件是MyBatis中最常用的插件之一,其基本原理是:

  1. 拦截Executor的query方法

  2. 判断是否需要分页(根据参数中的Page对象)

  3. 获取原始SQL并改写为分页SQL(不同数据库语法不同)

  4. 执行COUNT查询获取总记录数

  5. 执行分页查询获取当前页数据

  6. 将结果封装到Page对象中返回

五、插件开发注意事项

1. 性能考虑

插件会增加方法调用的层级,可能对性能产生一定影响。因此需要:

  • 避免在插件中执行耗时操作

  • 尽量减少不必要的拦截判断

  • 使用缓存避免重复计算

2. 线程安全问题

插件通常是单例的,需要确保线程安全:

  • 避免使用实例变量保存状态信息

  • 如需保存线程相关信息,使用ThreadLocal

  • 确保对共享资源的访问是线程安全的

3. 避免过度拦截

只拦截必要的方法,避免对MyBatis的正常执行造成不必要的影响:

  • 精确指定@Signature的method和args参数

  • 在intercept方法中尽早判断是否需要处理当前调用

4. 正确处理异常

在插件中需要妥善处理异常:

  • 不要吞没异常,除非有明确理由

  • 记录足够的调试信息以便排查问题

  • 考虑异常对事务的影响

六、插件应用场景

  1. 性能监控:记录SQL执行时间、慢查询监控

  2. 分页处理:统一的分页逻辑实现

  3. 数据权限:根据用户权限动态添加查询条件

  4. SQL改写:优化SQL、添加 hint、替换表名等

  5. 日志记录:详细的SQL日志输出,包括参数值

  6. 数据加解密:在参数设置和结果处理时进行数据加解密

  7. 多租户支持:自动添加租户ID过滤条件

  8. 审计字段处理:自动填充创建时间、修改时间等字段

七、总结

MyBatis插件机制通过动态代理和责任链模式的巧妙结合,提供了一个强大且灵活的功能扩展方式。理解其核心原理和实现细节,有助于我们更好地使用和开发自定义插件。在开发插件时,需要充分考虑性能、线程安全和适用场景,确保插件的稳定性和有效性。

通过合理使用插件机制,我们可以在不修改MyBatis源码的情况下,实现各种复杂的功能需求,大大提高了开发效率和系统的可维护性。


往期免费源码 (无删减,无套路):🔥🔥🔥  

https://pan.baidu.com/s/1sjAr08PU9Xe7MQf1gjGM5w?pwd=6666​

「在线考试系统源码(含搭建教程)」 (无删减,无套路):🔥🔥🔥  

链接:https://pan.quark.cn/s/96c4f00fdb43 提取码:WR6M

往期免费源码对应视频:

免费获取--SpringBoot+Vue宠物商城网站系统

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

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

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

🔥🔥🔥  有兴趣可以联系我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值