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

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

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

大纲

  • 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");
  }
}



		



相关推荐

C++企业级开发规范指南(c++开发gui)

打造高质量、可维护的C++代码标准一、前言C++作为一门功能强大的系统级编程语言,被广泛应用于操作系统、游戏引擎、高性能服务器、数据库系统等领域。知名互联网公司(如Google、Microsoft、腾...

C++|整型的最值、上溢、下溢、截断、类型提升和转换

整数在计算机内以有限字长表示,当超出最值(有限字长)时,需要截断(溢出,求模)操作。不同字长的整型具有不同的值域,混合运算时,需要类型提升和转换。1整形最值在<limit.h>中有整型的...

C++|漫谈STL细节及内部原理(c++ std stl)

1988年,AlexanderStepanov开始进入惠普的PaloAlto实验室工作,在随后的4年中,他从事的是有关磁盘驱动器方面的工作。直到1992年,由于参加并主持了实验室主任BillWo...

C++11新特性总结 (二)(c++11新特性 pdf)

1.范围for语句C++11引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素vector<int>vec={1,2,3,4,5,6};f...

C++ STL 漫谈(c++中的stl到底指的什么)

标准模板库(StandardTemplateLibrary,STL)是惠普实验室开发的一个函数库和类库。它是由AlexanderStepanov、MengLee和DavidRMusser在...

C++学习教程_C++语言随到随学_不耽误上班_0基础

C++学习教程0基础学C++也可以,空闲时间学习,不耽误上班.2019年C语言新课程已经上线,随到随学,互动性强,效果好!带你征服C++语言,让所有学过和没有学过C++语言的人,或是正准备学习C++语...

C++遍历vector元素的四种方式(c++ 遍历vector)

vector是相同类型对象的集合,集合中的每个对象有个对应的索引。vector常被称为容器(container)。C++中遍历vector的所有元素是相当常用的操作,这里介绍四种方式。1、通过下标访问...

一起学习c++11——c++11中的新增的容器

c++11新增的容器1:array当时的初衷是希望提供一个在栈上分配的,定长数组,而且可以使用stl中的模板算法。array的用法如下:#include<string>#includ...

C++编程实战基础篇:一维数组应用之投票统计

题目描述班上有N个同学,有五位候选人“A,B,C,D,E”,请所有的同学投票并选举出班长,现在请你编写程序来他们计算候选人的得票总数,每位同学投票将以数字的形式投票“12345”分别代表五位候选人,...

C++20 新特性(6):new表达式也支持数组大小推导

new表达式也支持数组大小推导在C++17标准中,在定义并初始化静态数组时,是可以忽略数组大小,然后通过初始化数据来推导数组的大小。但使用new来定义并初始化动态数组时,并不支持这种自动推导数组大...

C++ 结构体(struct)最全详解(c++结构体用法)

一、定义与声明1.先定义结构体类型再单独进行变量定义structStudent{intCode;charName[20];charSex;intA...

自学 C++ 第 6 课 二维数组找最值

键盘输入一个m×n的二维数组,通过C++编程找出元素中的最大值,并输出其所在的位置坐标。例如,输入一个4×5的二维数组,数组元素分别为{{556623749},{578964563},...

从缺陷中学习C/C++:聊聊 C++ 中常见的内存问题

在写C/C++程序时,一提到内存,大多数人会想到内存泄露。内存泄露是一个令人头疼的问题,尤其在开发大的软件系统时。一个经典的现象是,系统运行了10天、1个月都好好的,忽然有一天宕机了:OOM(Out...

C++开发者都应该使用的十个C++11特性(上)

在C++11新标准中,语言本身和标准库都增加了很多新内容,本文只涉及了一些皮毛。不过我相信这些新特性当中有一些,应该成为所有C++开发者的常规装备。你也许看到过许多类似介绍各种C++11特性的文章。下...

深度解读C/C++指针与数组(c++指针和数组的区别)

指针和数组是密切相关的。事实上,指针和数组在很多情况下是可以互换的。例如,一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。今天我们就来聊一聊数组和指针千丝万缕的关系;一维数组与...