当前位置: 首页 > article >正文

插件原理与开发

插件原理与开发

在 Mybatis总体执行流程 一文中简单的介绍了插件的初始化过程,本文将从源码的角度介绍一下mybatis的插件原理与简单开发实战。

插件原理

插件的注册和管理是通过InterceptorChain进行的,在创建Executor、StatementHandler、ParameterHandler、ResultSetHandler对象时,会执行InterceptorChain的pluginAll方法

  public Object pluginAll(Object target) {
    // 遍历所有的插件
    for (Interceptor interceptor : interceptors) {
      // 执行插件的plugin方法,返回代理对象
      target = interceptor.plugin(target);
    }
    return target;
  }

拦截的原理,正是此时返回的代理对象,当调用目标方法时,执行的就是拦截器的intercept方法,从而实现拦截功能。

      // 执行插件的plugin方法,返回代理对象
      target = interceptor.plugin(target);

来到Interceptor接口的plugin方法:

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

这是一个默认方法,一般不会重写它的逻辑。看其实现Plugin#wrap:

  public static Object wrap(Object target, Interceptor interceptor) {
    // 拿到拦截器的@Intercepts注解信息:key是要拦截的接口,value是要拦截的接口方法集合
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 这里的target,就是拦截的对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler对象)
    Class<?> type = target.getClass();
    // 返回包含在signatureMap中的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 存在被拦截的接口,返回一个代理对象
    if (interfaces.length > 0) {
      // 利用jdk动态代理生成代理对象:关注Plugin(实现了InvocationHandler接口)的invoke方法
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // 接口没有被拦截,返回原始对象
    return target;
  }

可以看到,如果接口被拦截了,就会利用JDK动态代理生成代理对象,由于Plugin实现了InvocationHandler接口,所以其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)) {
        // 执行自定义拦截器的intercept方法,并将目标对象、方法、参数传入
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // 否则直接执行原始方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

插件开发

自定义插件需要:

  1. 实现Interceptor接口,重写intercept方法

  2. 使用@Intercepts和@Signature注解表明需要拦截哪些类的哪些方法

  3. 在配置文件中,添加插件配置

mybatis官网中,对此也有所描述:mybatis – MyBatis 3 | Configuration

根据官网描述,mybatis插件可以拦截的方法如下:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

以下是我写的一个记录SQL及其耗时的拦截器,加深对拦截器的理解:

/**
 * @Author: qiuxinfa
 * @CreateTime: 2023-12-07  22:15
 * @Description: 自定义拦截器:打印SQL、统计SQL执行时间
 */
@Intercepts({
        @Signature(type = StatementHandler.class,method = "batch",args = {Statement.class}),
        @Signature(type = StatementHandler.class,method = "update",args = {Statement.class}),
        @Signature(type = StatementHandler.class,method = "query",args = {Statement.class, ResultHandler.class}),
})
public class SqlLogPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取执行的SQL
        String sql;
        Statement statement=(Statement) invocation.getArgs()[0];
        if(Proxy.isProxyClass(statement.getClass())){
            MetaObject metaObject= SystemMetaObject.forObject(statement);
            Object h = metaObject.getValue("h");
            if(h instanceof StatementLogger){
                RoutingStatementHandler rsh=(RoutingStatementHandler) invocation.getTarget();
                sql = rsh.getBoundSql().getSql();
            }else {
                PreparedStatementLogger psl=(PreparedStatementLogger) h;
                sql = psl.getPreparedStatement().toString();
            }
        }else{
            sql = statement.toString();
        }
        // 记录开始时间
        long start = System.currentTimeMillis();
        // 执行目标方法
        Object result = invocation.proceed();
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.err.println("执行SQL ===> ");
        System.err.println(sql);
        System.err.println("统计SQL耗时 = " + (end - start) + "毫秒");
        System.err.println("返回结果 =======> " + result);
        return result;
    }
}

配置文件添加插件:

    <!-- 配置插件    -->
    <plugins>
        <plugin interceptor="com.qxf.plugin.SqlLogPlugin"></plugin>
    </plugins>

配置之后,会打印执行的SQL语句及其耗时。


http://www.kler.cn/a/161369.html

相关文章:

  • 【C语言】值传递和地址传递
  • Qt_day4_Qt_UI设计
  • MyBatisPlus 用法详解
  • 《DiffusionDet: Diffusion Model for Object Detection》ICCV2023
  • 【计算机网络】Socket编程接口
  • 【JAVA】正则表达式中的中括弧
  • Tap虚拟网卡
  • 如何使用 Oracle SQL Developer 连接 pgvector
  • 【蓝桥杯选拔赛真题73】Scratch烟花特效 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析
  • Linux 线程——信号量
  • c++实验多态程序设计
  • Linux下安装Docker与Docker Compose
  • 数据库的索引
  • 3D模型格式转换工具HOOPS Exchange的典型应用场景介绍
  • 深入了解Java Period类,对时间段的精确控制
  • 理解js中原型链的封装继承多态
  • ky10 server x86在线安装ffmpeg
  • 在安全环境中使用虚拟化进行隔离——Armv8.4上的安全世界软件架构
  • Python插件 - 动态 简单账表 通过SQL存储过程输出列实现动态展示
  • Apache Hive(部署+SQL+FineBI构建展示)
  • 【Element】el-table组件使用summary-method属性设置表格底部固定两行并动态赋值
  • SCI常用的连接词
  • SpringBoot自动重启
  • solidity案例详解(六)服务评价合约
  • 集成开发环境 PyCharm 的安装【侯小啾python基础领航计划 系列(二)】
  • ARM虚拟化与车联网安全应用