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

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

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

大纲

  • 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数据库凭借其开源、高效等特性被广泛应用。而索引,作为提升MySQL查询性能的关键利器,能大幅加速数据检索。然而,在实际开发中,即便精心创建了索引,却常常遭遇索引失...

15分钟,带你了解indexedDB,这个前端存储方案很重要!

原文来源于:程序员成长指北;作者:Django强哥如有侵权,联系删除最近在给前端班授课,在这次之前的最后一次课已经是在2年前,2年的时间,前端的变化很大,也是时候要更新课件了。整理客户端存储篇章时模糊...

MySQL 面试总被问到的那些问题,你都懂了吗?

事务的四大特性是什么?首先得提一下ACID,这可是数据库事务的灵魂所在:原子性(Atomicity):要么全部成功,要么全部失败回滚。一致性(Consistency):确保数据在事务前后都处于一致状态...

Java 字符串常见的操作_java字符串总结

在Java当中,为字符串类提供了丰富的操作方法,对于字符串,我们常见的操作就是:字符串的比较、查找、替换、拆分、截取以及其他的一些操作。在Java中,有String,StringBuffer和St...

java学习分享:Java截取(提取)子字符串(substring())

在String中提供了两个截取字符串的方法,一个是从指定位置截取到字符串结尾,另一个是截取指定范围的内容。下面对这两种方法分别进行介绍。1.substring(intbeginIndex)形...

你必须知道的 7 个杀手级 JavaScript 单行代码

1.如果你需要一个临时的唯一ID,请生成随机字符串。这个例子将为你生成一个随机字符串:constrandomString=Math.random().toString(36).slice(2)...

MySQL 索引失效:原因、场景与解决方案

在互联网软件开发领域,MySQL作为一款广泛使用的关系型数据库,其性能优化至关重要。而索引,作为提升MySQL查询性能的关键手段,一旦失效,会导致查询效率大幅下降,影响整个系统的性能。今天,就来...

Axure9 教程:可模糊搜索的多选效果

一、交互效果说明1.点击话题列表中的话题选项,上方输入框内显示选择的话题标签,最多可选择5个标签,超出将有文字提示。2.点击输入框内已选择的话题标签的删除按钮,可以删除已选择的话题标签,并且该标签返回...

JavaScript字符串操作方法大全,包含ES6方法

一、charAt()返回在指定位置的字符。...

为什么MySQL索引不生效?来看看这8个原因

在数据库优化中,最让人头疼的事情之一莫过于精心设计的索引没有发挥作用。为什么会出现这种情况?这篇文章带大家一起探讨一些常见原因,方便大家更好地理解MySQL查询优化器是如何选择索引的,以及在出现类...

Kettle实现rabbitMQ的生产与消费_rabbitmq不支持顺序消费

文章目录一、Kettle为什么可以读取流数据?...

MySQL高频函数Top10!数据分析效率翻倍,拒绝无效加班!

引言:为什么你的SQL代码又臭又长?“同事3行代码搞定的事,你写了30行?”“每次处理日期、字符串都抓狂,疯狂百度?”——不是你不努力,而是没掌握这些高频函数!本文精炼8年数据库开发经验,总结出10个...

mysql的截取函数用法详解_mysql截取指定字符

substring()函数测试数据准备:用法:以下语法是mysql自动提示的1:substirng(str,pos):从指定位置开始截取一直到数据完成str:需要截取的字段的pos:开始截取的位置。从...

MySQL函数:字符串如何截取_mysql 字符串截取函数

练习截取字符串函数(五个)mysql索引从1开始...

数据集成产品分析(一)_数据集成工具有哪些

编辑导语:数据集成产品是数据中台建设的第一环节,在构建数据中台或大数据系统时,首先要将企业内部各个业务系统的数据实现互联互通,从物理上打破数据孤岛。本文作者对数据集成产品进行了分析,一起来看一下吧。数...