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

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

ztj100 2025-02-07 19:55 9 浏览 0 评论

大纲

  • 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, Set> signatureMap;

  /**
   * 代理工厂方法: 用于为目标对象生成jdk 动态代理对象
   * @param target 目标对象: 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler) 
   * @param interceptor PluginProxy 代理对象本身
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    //如果方法是@Intercepts注解中指定的方法,则执行拦截逻辑
    Map, 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, 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()方法返回的代理对象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");
  }
}



		



相关推荐

利用navicat将postgresql转为mysql

导航"拿来主义"吃得亏自己动手,丰衣足食...

Navicat的详细教程「偷偷收藏」(navicatlite)

Navicat是一套快速、可靠并价格适宜的数据库管理工具,适用于三种平台:Windows、macOS及Linux。可以用来对本机或远程的MySQL、SQLServer、SQLite、...

Linux系统安装SQL Server数据库(linux安装数据库命令)

一、官方说明...

Navicat推出免费数据库管理软件Premium Lite

IT之家6月26日消息,Navicat推出一款免费的数据库管理开发工具——NavicatPremiumLite,针对入门级用户,支持基础的数据库管理和协同合作功能。▲Navicat...

Docker安装部署Oracle/Sql Server

一、Docker安装Oracle12cOracle简介...

Docker安装MS SQL Server并使用Navicat远程连接

...

Web性能的计算方式与优化方案(二)

通过前面《...

网络入侵检测系统之Suricata(十四)——匹配流程

其实规则的匹配流程和加载流程是强相关的,你如何组织规则那么就会采用该种数据结构去匹配,例如你用radixtree组织海量ip规则,那么匹配的时候也是采用bittest确定前缀节点,然后逐一左右子树...

使用deepseek写一个图片转换代码(deepnode处理图片)

写一个photoshop代码,要求:可以将文件夹里面的图片都处理成CMYK模式。软件版本:photoshop2022,然后生成的代码如下://Photoshop2022CMYK批量转换专业版脚...

AI助力AUTOCAD,生成LSP插件(ai里面cad插件怎么使用)

以下是用AI生成的,用AUTOLISP语言编写的cad插件,分享给大家:一、将单线偏移为双线;;;;;;;;;;;;;;;;;;;;;;单线变双线...

Core Audio音频基础概述(core 音乐)

1、CoreAudioCoreAudio提供了数字音频服务为iOS与OSX,它提供了一系列框架去处理音频....

BlazorUI 组件库——反馈与弹层 (1)

组件是前端的基础。组件库也是前端框架的核心中的重点。组件库中有一个重要的板块:反馈与弹层!反馈与弹层在组件形态上,与Button、Input类等嵌入界面的组件有所不同,通常以层的形式出现。本篇文章...

怎样创建一个Xcode插件(xcode如何新建一个main.c)

译者:@yohunl译者注:原文使用的是xcode6.3.2,我翻译的时候,使用的是xcode7.2.1,经过验证,本部分中说的依然是有效的.在文中你可以学习到一系列的技能,非常值得一看.这些技能不单...

让SSL/TLS协议流行起来:深度解读SSL/TLS实现1

一前言SSL/TLS协议是网络安全通信的重要基石,本系列将简单介绍SSL/TLS协议,主要关注SSL/TLS协议的安全性,特别是SSL规范的正确实现。本系列的文章大体分为3个部分:SSL/TLS协...

社交软件开发6-客户端开发-ios端开发验证登陆部分

欢迎订阅我的头条号:一点热上一节说到,Android客户端的开发,主要是编写了,如何使用Androidstudio如何创建一个Android项目,已经使用gradle来加载第三方库,并且使用了异步...

取消回复欢迎 发表评论: