首页 > 基础资料 博客日记

Java-14 深入浅出 MyBatis - 插件机制 插件原理 自定义插件 源码分析

2025-01-15 10:30:08基础资料围观58

本篇文章分享Java-14 深入浅出 MyBatis - 插件机制 插件原理 自定义插件 源码分析,对你有帮助的话记得收藏一下,看Java资料网收获更多编程知识

点一下关注吧!!!非常感谢!!持续更新!!!

大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html

目前已经更新到了:

  • MyBatis(正在更新)

插件简介

一般情况下,开源框架都会提供插件或者扩展点,供开发者自行拓展。这样的好处显而易见,一是增加了框架的灵活性,二是开发者结合实际需求,对框架进行扩展,使它能够更好的工作。
以 MyBatis 为例子,我们可以基于 MyBatis 插件机制实现分页、分表、监控功能。由于插件和业务无关,业务也无法感知插件的存在,因此可以无感植入插件,在无形中实现增强。
MyBatis 插件机制允许用户通过实现插件接口来扩展 MyBatis 的功能,拦截 SQL 语句的执行过程,进行自定义的处理。这是 MyBatis 提供的一种灵活的机制,可以在不修改核心代码的情况下对其行为进行定制。插件可以用于日志记录、性能监控、SQL 统计等多个场景。

基本概念

MyBatis 插件机制基于 Java 的 拦截器模式,通过插件类实现对 MyBatis 核心行为的拦截和修改。插件可以在执行特定的操作时(如查询、更新、删除等)插入自定义代码。常见的插件场景包括:

  • 拦截执行 SQL 的过程:可以修改、增强或记录 SQL 执行的细节。
  • 增加额外的处理逻辑:比如日志、缓存、性能监控等。

作用范围

插件的作用对象是 MyBatis 内部的核心组件,如 Executor、StatementHandler、ResultSetHandler 和 ParameterHandler。你可以选择拦截这些组件的具体操作,例如:

  • Executor:执行 SQL 的操作。
  • StatementHandler:处理 SQL 语句的生成、参数设置等。
  • ResultSetHandler:处理结果集的映射。
  • ParameterHandler:处理 SQL 语句的参数设置。

插件通过 intercept 方法拦截目标对象,决定是否执行后续操作或修改返回值。

常见场景

  • SQL 日志记录: 记录执行的 SQL 语句和执行时间,有助于进行性能监控和问题排查。
  • SQL 性能监控: 插件可以记录 SQL 执行的时间,帮助开发者识别性能瓶颈,优化 SQL 执行。
  • 事务控制: 插件可以用来在执行 SQL 前后添加自定义的事务控制逻辑,如重试机制等。
  • 缓存: 插件可以用于实现二级缓存或其他缓存机制,避免重复查询相同数据。
  • SQL 改写: 插件可以修改 SQL 语句,例如添加某些条件、修改排序等,扩展 MyBatis 的查询功能。

优点

  • 灵活性:插件机制允许开发者在不修改 MyBatis 核心代码的情况下,灵活地增加或修改功能。
  • 扩展性:插件可以轻松地扩展 MyBatis,满足各种业务需求。
  • 可定制性:可以根据业务需求定制插件,实现特定的功能,如日志记录、性能监控等。

缺点

  • 调试复杂:由于插件是通过代理模式实现的,调试时可能会增加复杂性。
  • 性能开销:每次 SQL 执行时都需要经过插件的拦截和处理,可能会引入一定的性能开销。

插件介绍

MyBatis 作为一个广泛的优秀的 ORM 开源框架,这个框架具有强大的灵活性,在四大组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler,提供了简易易用的插件扩展机制。
MyBatis 对持久层的操作就是依赖这四个核心对象,MyBatis 支持用插件对四大核心对象进行拦截,对 MyBatis 来说就是拦截器,增强核心对象的功能,增加功能本质上借助底层的动态代理来实现的,换句话说,MyBatis 中的四大对象都是代理对象。

MyBatis 允许拦截的方法如下:

  • 执行器 Executor:update query commit rollback 等等
  • SQL 语法构建器 StatementHandler:prepare、parameterize、batch、updates query 等方法
  • 参数处理器 ParameterHandler:getParameterObject setParameters 方法
  • 结果集处理器 ResultSetHandler:handlerResultSets handleOutputParameters 等方法

插件原理

在四大核心对象创建的时候:

  • 每个创建出来都不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler)
  • 获取到所有 interceptor,调用 interceptor.plugin(target)返回 target 包装后的对象
  • 插件机制,可以使用插件为目标对象创建一个代理对象,AOP(面向切面)我们的插件可以为四大对象创建出代理对象,代理对象可以拦截到四大对象的每一个执行。

插件具体是如何拦截并附加额外的功能呢?比如 ParameterHandler:

public ParameterHandler createParameterHandler(
        MappedStatement mappedStatement, 
        Object parameter, 
        BoundSql boundSql, 
        InterceptorChain interceptorChain) {

    // 创建 ParameterHandler
    ParameterHandler parameterHandler = 
            mappedStatement.getLang().createParameterHandler(mappedStatement, parameter, boundSql);

    // 通过拦截链对 ParameterHandler 进行增强
    parameterHandler = (ParameterHandler) interceptorChain.applyInterceptors(parameterHandler);

    return parameterHandler;
}

public Object applyInterceptors(Object target) {
    // 对目标对象依次应用所有拦截器
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

interceptorChain 保存了所有的拦截器(interceptors),是 MyBatis 初始化的时候创建的,调用拦截器链中的拦截器依次对目标进行拦截或增强。interceptor.plugin(target)中的 target 就可以理解为 MyBatis 中的四大对象,返回的 target 就是被多重代理后的对象。

如果我们想要拦截 Executor 的 query 方法,那么可以定义:

@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class ExamplePlugin implements Interceptor {
    
    // 这里是插件的主要逻辑部分
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 插件逻辑
        return invocation.proceed(); // 执行原方法
    }

    // 这个方法用于插件的封装和配置
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this); // 创建插件代理对象
        }
        return target;
    }

    // 这个方法用于插件的参数配置
    @Override
    public void setProperties(Properties properties) {
        // 设置插件的配置参数
    }
}

除此之外,我们还需要将插件配置到 sqlMapConfig.xml 上:

<plugins>
    <plugin interceptor="icu.wzk.interceptor.ExamplePlugin"></plugin>
</plugins>

这样 MyBatis 在启动时可以加载插件,并保存插件实例相关对象(InterceptorChain,拦截器)。待准备工作结束后,MyBatis 对于就绪状态,我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SQL Session。Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为实例生成代理类,这样,插件逻辑可以在 Executor 相关方法开始调用前执行。

自定义插件

MyBatis 插件接口 interceptor:

  • interceptor 方法,插件的核心方法
  • plugin 方法,生成 target 的代理对象
  • setProperties 方法,传递插件所需参数

源码分析

插件执行逻辑,Plugin 实现了 InvocationHandler 接口,因此它的 Invoke 方法会拦截所有的方法调用,invoke 方法会对所有拦截的方法进行检测,以决定是否执行插件逻辑,该方法的逻辑如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

对应的截图如下所示:

可以看到,invoke 方法的代码比较少,逻辑不难理解,首先,invoke方法会检测被拦截方法,插件逻辑封装在 interceptor 中,该方法的参数类型为 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 getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

对应的截图如下所示:


文章来源:https://blog.csdn.net/w776341482/article/details/144150048
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云