2.3MyBatis——插件机制

news/2024/10/6 15:56:15 标签: mybatis, java, 源码学习

2.3MyBatis——插件机制

  • 1.基本用法
  • 2.原理探究
    • 2.1加载过程
    • 2.2执行过程
      • 2.2.1 插件的执行点
      • 2.2.2 SQL执行的几个阶段
      • 2.2.3 如何梳理出执行流程

插件机制是一款优秀框架不可或缺的组成部分,比如spring、dubbo,还有我们要聊的Mybatis等等。所谓插件,通俗一点说,就是框架提供了一个入口,允许你通过实现框架提供的扩展接口,来进行功能增强。比如spring的前置处理器允许你在Bean创建的过程中添加自定义逻辑。


具体到Mybatis框架,它的核心是ORM和SQL的映射。那么映射成功的SQL在具体执行的过程中,存在许多时机,比如参数处理阶段,SQL语句处理阶段,SQL执行阶段,返回结果的处理阶段等。在这些阶段或者说执行点,如果框架提供了扩展点,那么我们就可以通过插件在相应的环节添加一些能力。

1.基本用法

因为Mybatis的插件并不像SQL映射那样被经常使用,所以先看一下插件的基本用法。根据Mybatis官方的文档说明,只要实现Interceptor接口,然后注册插件即可。

java">//测试使用Springboot,插件放入Springboot容器后通过ObjectProvider注入到Configuration对象中完成注册
@Component 
@Intercepts({ // 声明作用点,即拦截哪些方法
        @Signature(
                type = Executor.class, // 这里拦截执行器的query方法
                method = "query", 
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class MyPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("自定义插件:统计运行时长");
        long begin = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        System.out.println((end - begin) /1000 + "s");
        return result;
    }
}

这里定义拦截器的方式类似定义切点,type的常用取值为(下文会详细介绍):

  • Executor
  • StatementHandler
  • ParameterHandler
  • ResultSetHandler

这些接口涵盖了SQL执行的各个阶段。method取值则是type接口中对应的方法,如果方法存在重载,还可以通过args进行限定。

2.原理探究

当mapper接口中的方法执行时,插件被调用并统计时长。关于Mybatis插件,使用时很自然的疑问是:它是如何被加载注册,又是如何被调用执行的(即它的设计原理是什么)?如果你没有疑问,那就现在发出疑问…
然后我们从这两点去分析和学习。

2.1加载过程

我们知道在Mybatis中,不管是以哪种方式配置Mybatis,Mybatis的配置信息和mapper.xml的数据最终都会以Configuration对象的形式存在,Configuration它是Mybatis的核心组件之一。
如果不使用Springboot,集成Mybatis需要创建相应的配置文件,在配置文件中进行Mybatis的相关配置,包括插件注册。所以猜想插件信息也是被收集到Configuration对象中。我们浅浅看一下代码:

java">// 1. configuration对象提供了添加拦截器的方法
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
// 2 XML 配置文件解析时会调用addInterceptor,解析后通过add进行注册
pluginElement(root.evalNode("plugins"));// 解析plugins节点

// 3.1 springboot 项目中,通过ObjectProvider获取所有的Interceptor实现类,然后完成注册
public MybatisAutoConfiguration(ObjectProvider<Interceptor[]> interceptorsProvider,
      ....) {
    this.interceptors = interceptorsProvider.getIfAvailable();
    .....
  }
@Bean
@ConditionalOnMissingBean
 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
...
if (!ObjectUtils.isEmpty(this.interceptors)) {
  	factory.setPlugins(this.interceptors);// 注册所有拦截器实现类
}
...
}

这里只需要明白一点,拦截器的实现类(即插件),最终都会通过addInterceptor被添加到核心组件Configuration对象的interceptorChain属性中。其他的诸如SQL映射(即mappedStatements)、转换器等都是同样的加载逻辑。

2.2执行过程

2.2Mybatis——代理与SQL映射这篇博客中,只聊到动态代理MapperProxy是如何将mapper接口与mapper.xml进行关联的。至于SQL的后续执行没有描述,下面我们通过研究插件的执行,顺带了解一下SQL的执行过程

2.2.1 插件的执行点

既然是介绍插件机制,自然是以插件的执行作为切入点。由于Mybatis的执行过程中涉及多个代理对象的调用,如果在调试过程中发现程序反复横跳,可以先不纠结,你应该至少能发现一点:在Configuration对象中,有多个方法调用了pluginAll

java">public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 遍历执行插件逻辑
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
  // newResultSetHandler newStatementHandler newExecutor
  
// pluginAll
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

newResultSetHandler newStatementHandler newExecutor newParameterHandler四个方法中,都调用了pluginAll,也就是说在这些方法被调用时,都可以植入插件逻辑。

2.2.2 SQL执行的几个阶段

如果想要精确控制插件在SQL执行的哪个环节生效,就必须要知道Mybatis中SQL执行分几个阶段,并且每个阶段和newResultSetHandler newStatementHandler newExecutor newParameterHandler的对应关系。
SQL执行的先后顺序 :

  1. 创建执行器对象,对应 newExecutor()
  2. 创建SQL语句对象,对应 newStatementHandler()
  3. 参数处理器, 对应newParameterHandler()
  4. 结果集处理器,对应newParameterHandler()

知道了各个阶段和对应的方法,这样才能精确的控制拦截器生效的时机。比如文章开头定义的插件,它将作用在newExecutor()执行时,即SQL执行的最开始阶段。

2.2.3 如何梳理出执行流程

前面提到,插件的执行流程中,由于涉及到多个代理对象的调用,所以调试流程不像普通的栈帧嵌套。调试过程如果觉得不顺,建议多去理解2.2Mybatis——代理与SQL映射这篇文章中关于JDK的代理实现。代理对象对接口方法的调用就是调用处理器中invoke方法的执行。明白了这一点,上述的插件执行点和SQL执行阶段都可以梳理出来。我们简单分析一下流程:

  1. MapperProxy确定SQL映射关系后,调用MapperMethod.execute方法,后面开始执行SQL
    在这里插入图片描述
  2. 由于使用的是mybatis-spring-boot-starter依赖,所以这里的sqlSessionSqlSessionTemplate实例,可以看到,SqlSessionTemplate类中持有的sqlSessionProxy就是一个JDK代理对象,所以this.sqlSessionProxy.selectOne()实际是调用SqlSessionInterceptorinvoke方法
    在这里插入图片描述
  3. 继续调试,当调用拦截器链的pluginAll()方法时,内部是调用拦截器的plugin方法。MyBatis内部有Plugin插件类,为啥自定义插件却要实现拦截器接口呢?(拦截器就是插件的逻辑部分)
    在这里插入图片描述
  4. Plugin对象也是一个调用处理器,即上面代码中Plugin.wrap返回的是一个代理对象,在调用真实对象之前执行代理逻辑,这里的代理逻辑就是拦截器逻辑
    在这里插入图片描述
  5. 看到这里你应该明白,方法依次返回后,pluginAll返回的是一个JDK代理对象,executor为例,代理对象代理了executor,且代理的逻辑就是在executor执行指定方法前,调用插件逻辑(第四步invoke逻辑),这就是Mybatis插件的原理
    在这里插入图片描述

http://www.niftyadmin.cn/n/5691841.html

相关文章

几个卷积神经网络(CNN)可视化的网站

以下是一些提供卷积神经网络&#xff08;CNN&#xff09;动画和可视化的有用网址&#xff1a; CNN Explainerhttps://poloclub.github.io/cnn-explainer/ 直观的交互式工具&#xff0c;解释卷积神经网络的基本原理。 Deep Learning Playgroundhttps://playground.tensorflow.o…

计算机网络(十) —— IP协议详解,理解运营商和全球网络

目录 一&#xff0c;关于IP 1.1 什么是IP协议 1.2 前置认识 二&#xff0c;IP报头字段详解 三&#xff0c;网段划分 3.1 IP地址的构成 3.2 网段划分 3.3 子网划分 3.4 IP地址不足问题 四&#xff0c;公网IP和私有IP 五&#xff0c;理解运营商和全球网络 六&#xff…

【Mac】和【安卓手机】 通过有线方式实现投屏

Mac 和 安卓手机 实现投屏&#xff08;有线&#xff09; 1.下载HomeBrew /bin/bash -c "$(curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/install/master/install.sh)"2.安装Scrcpy brew install scrcpy3.安装adb brew install android-platfor…

python和c

python这个语言现在用的非常流行了。拥护它的人高度赞美它&#xff0c;认为它高效率&#xff0c;功能强&#xff0c;现代化&#xff0c;限于自己的水平完全不知道它有什么问题。“因为无知而完美”。这样的语言&#xff0c;就给这样的人用。 我一直觉得python没什么。这不过是…

音视频入门基础:FLV专题(13)——FFmpeg源码中,解析任意Type值的SCRIPTDATAVALUE类型的实现

一、SCRIPTDATAVALUE类型 从《音视频入门基础&#xff1a;FLV专题&#xff08;9&#xff09;——Script Tag简介》中可以知道&#xff0c;根据《video_file_format_spec_v10_1.pdf》第80到81页&#xff0c;SCRIPTDATAVALUE类型由一个8位&#xff08;1字节&#xff09;的Type和…

第八篇:磁盘管理(1)

目录 6.1分区 6.1.1基本分区 6.1.1.1磁盘的相关知识 6.1.1.1.1基础知识 6.1.1.1.2命名 1.对于串口硬盘&#xff1a;/dev/sda、/dev/sdb、/dev/sdc......往后都是一个字母一个字母的累加 2.对于并口硬盘&#xff1a;/dev/hda其余相同 6.1.1.1.3磁盘的分区方式 方式1&am…

MATLAB GUI组件全解析:构建交互式应用程序

MATLAB的图形用户界面&#xff08;GUI&#xff09;是一个功能强大的工具&#xff0c;它允许开发者创建直观且用户友好的界面。这些界面&#xff0c;也称为应用程序或app&#xff0c;提供了点击控制&#xff0c;使得用户无需学习编程语言或输入命令即可运行应用程序。本文将详细…

基于springboot vue 电影推荐系统

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…