百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

mybatis3 源码深度解析-mybatis插件实现原理分析-jdk动态代理

wptr33 2025-03-06 20:09 24 浏览

大纲

  • mybatis 插件使用
  • mybatis 插件涉及相关类介绍
  • mybatis 插件实现原理
  • 自定义分页分页插件

mybatis 插件使用

mybatis 插件通过实现自定义拦截器的方式可以改变 sql 的执行行为

//1 编写自定义插件
@Intercepts({// 使用这个注解,声明这是一个 mybaits拦截器  
  //要拦截方法签名(type:被拦截方法所在的类,method:被拦截方法名,args:被拦截方法参数列表类型
  @Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})
})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}



  
    
  

mybatis 插件涉及相关类介绍

  • Configuration 类中维护了InterceptorChain拦截器链对象
  • 在 Configuration 解析创建过程中会解析 标签,将插件维护到interceptorChain
public class Configuration {
  // mybatis插件列表  List interceptors = new ArrayList<>();
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  //在 Configuration 解析创建过程中会解析 标签,将插件维护到interceptorChain
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
}
//InterceptorChain对象 是一个拦截器链对象
public class InterceptorChain {
  private final List interceptors = new ArrayList<>();
 //遍历调用所有拦截器对象的 plugin()方法执行拦截逻辑
 //target 是传过来的执行器等4大天王(Executor,ParameterHandler,ResultSetHandler,StatementHandler)
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        // 使用jdk 创建executor的代理对象 ,循环 将target作为入参,一个嵌套一个target (责任链模式)
      target = interceptor.plugin(target);
    }
      //责任链 后面的代理executor,持有前面代理executor的引用
    return target;
  }
  • 默认情况下,MyBatis 允许使用插件来拦截的以下 4 个组件方法调用:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

  • MyBatis 为什么能实现对以上方法的拦截? Configuration类提供的工厂方法,实现了拦截逻辑
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  //调用interceptorChain.pluginAll(),创建ParameterHandler代理对象
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, BoundSql boundSql) {
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  //调用interceptorChain.pluginAll(),创建代理对象
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
   //调用interceptorChain.pluginAll(),创建代理对象
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
  //return newExecutor(transaction, defaultExecutorType);
  	//调用interceptorChain.pluginAll(),创建代理对象 ...
   //executor = (Executor) interceptorChain.pluginAll(executor);
}
  • 了解 Interceptor接口
/**
 * 插件在具体实现的时候,采用的是拦截器模式,要注册为mybatis插件,必须实现org.apache.ibatis.plugin.Interceptor接口
 * interceptor属性值既可以完整的类名,也可以是别名,只要别名在typealias中存在即可,如果启动时无法解析,会抛出ClassNotFound异常。
 */
public interface Interceptor {
  /**
   * @param invocation 封装目标对象的方法和参数
   * @return
   */
  Object intercept(Invocation invocation) throws Throwable;
  /**
   * 用于创建target 目标对象的的代理对象 (所以方法名更适合叫 createPluginProxy?)
   * @param target
   * @return
   */
  default Object plugin(Object target) {
    // 使用jdk 创建executor的代理对象
    return Plugin.wrap(target, this);
  }
  //用于设置插件的属性值
  default void setProperties(Properties properties) {
    // NOP
  }
}
//invocation 封装目标对象的方法和参数
public class Invocation {
  //目标对象 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler)
  private final Object target;
  //目标方法,拦截的方法
  private final Method method;
  //入参
  private final Object[] args;
  //执行目标方法
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}
  • 为了创建目标对象 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler) 的代理对象,mybatis 提供了一个 Plugin工具类(又或者叫PluginProxy?)
  • java实现动态代理的两种方式-jdk 和 cglib

在学习 jdk 动态代理的时候 ,了解到动态代理涉及以下 3 个主体

1 目标对象(被代理的对象)必须要实现业务接口, mybatis插件中的业务接口为 Interceptor

2 代理对象(代理类):必须实现 InvocationHandler 接口, mybatis插件中的代理对象为Plugin

3 使用 Proxy.newProxyInstance() 创建代理对象, 这里直接将创建代理对象的工具工厂方法写在了Plugin.wrap()方法中

public class Plugin implements InvocationHandler {
  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class, Set> signatureMap;

  /**
   * 代理工厂方法: 用于为目标对象生成jdk 动态代理对象
   * @param target 目标对象: 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler) 
   * @param interceptor PluginProxy 代理对象本身
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    //如果方法是@Intercepts注解中指定的方法,则执行拦截逻辑
    Map<Class, Set> signatureMap = getSignatureMap(interceptor);
    //CacheExecutor 对象对应的类
    Class type = target.getClass();
    //获取类型对应的接口 org.apache.ibatis.executor.Executor
    Class[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // 使用jdk 创建executor的代理对象
      return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  //在 jdk 动态代理中,所有代理对象会先执行invoke()
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set 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);
    }
  }
}

mybatis 插件实现原理

SqlSession会调用 Configuration.newExecutor() 工厂方法创建 Executor 对象,经过
interceptorChain.pluginAll()方法后其实已经是一个 jdk生成的动态代理对象了

//1 SqlSession会调用 Configuration.newExecutor() 工厂方法创建 Executor 对象
public Executor newExecutor(Transaction transaction) {
  //return newExecutor(transaction, defaultExecutorType);
  	//2.1 调用interceptorChain.pluginAll(),创建代理对象 ...
   // executor = (Executor) interceptorChain.pluginAll(executor);
}

public class InterceptorChain {
  private final List interceptors = new ArrayList<>();
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // 2.2 使用jdk 创建executor的代理对象 ,循环 将target作为入参,一个嵌套一个target (责任链模式)
      target = interceptor.plugin(target);
    }
    // 5 使用 for 循环遍历形成链链表 后面的代理executor,持有前面代理executor的引用
    //代理对象 :plugin3 → plugin2 → Plugin1 → Executor
    return target;
  }

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;
  default Object plugin(Object target) {
    // 3 Interceptor实现类,调用Plugin.wrap()静态方法,创建代理对象
    return Plugin.wrap(target, this);
  }
} 

public class Plugin implements InvocationHandler {
  private final Object target;
  private final Interceptor interceptor;

  /**
   * 4 代理工厂方法: 用于为目标对象生成jdk 动态代理对象
   * @param target 目标对象: 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler)
   * @param interceptor
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    //如果方法是@Intercepts注解中指定的方法,则执行拦截逻辑
    Map<Class, Set> signatureMap = getSignatureMap(interceptor);
    //CacheExecutor 对象对应的类
    Class type = target.getClass();
    //获取类型对应的接口 org.apache.ibatis.executor.Executor
    Class[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // 4.2 使用jdk 创建executor等目标对象的代理对象
      return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

当目标对象,执行完 pluginAll()方法返回的代理对象示例图

  • 当 pluginAll()方法返回的代理对象Executor 的某个方法被调用的时候,插件逻辑会先行执行。执行顺序由外而内,比如上图的执行顺序为 plugin3 → plugin2 → Plugin1 → Executor
  • 当 SqlSession执行 selectOne()方法时,经历过程如下

1 SqlSession执行 selectOne(),会委托给执行器执行

2 SqlSession会调用 Configuration.newExecutor() 工厂方法,获取到 Executor 动态代理对象后,

3 按照 jdk 动态代理机制,执行目标对象方法前会先执行 代理对象的 invoke() 方法,

4 而 plugin 中的 invoke()会调用拦截器对象的 intercept ()方法,执行拦截逻辑

5 执行完拦截器链的逻辑后,会调用目标对象方法,返回执行结果

插件逻辑执行过程


自定义分页分页插件

  • 默认情况下,MyBatis 允许使用插件来拦截的以下 4 个组件方法调用:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

//本示例对StatementHandler.prepare()方法进行拦截处理
public interface StatementHandler {
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
}
/**
 * 自定义 mybatis 分页插件
 * @author ouzhx on 2022-07-24.
 */
//1 编写自定义插件
@Intercepts({// 使用这个注解,声明这是一个 mybaits拦截器
  //要拦截方法签名(type:被拦截方法所在的类,method:被拦截方法名,args:被拦截方法参数列表类型
  @Signature(type= StatementHandler.class,method = "parepare",args = {Connection.class,Integer.class})
})
public class PageInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    //获取拦截的目标对象
    RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
    BoundSql boundSql = statementHandler.getBoundSql();
    //获取请求入参参数对象
    Object parameterObject = boundSql.getParameterObject();

    /**
     * 1 分页逻辑处理参考, 定义一个 Page 对象
     * 2 如果入参parameterObject instanceof Page ,将入参对象转为  Page 对象
     * 3 查询总记录数
     * 4 生成分页 sql
     * 5 将BoundSql对象原始的 sql 语句替换为分页语句,没有 setSql()方法可用反射设置字段值
     *
     */
    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
     this.xxx=properties.getProperty("xxx");
  }
}



		



相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...