MyBatis是纸老虎吗?(四)

在《MyBatis是纸老虎吗?(三)》这篇文章中我们一起梳理了MyBatis配置文件的解析流程,并详细介绍了其中的一些常见节点的解析步骤。通过梳理,我们弄清楚了MyBatis配置文件中的一些常用配置项与Java Bean之间的对应关系,这进一步加深了我们对MyBatis配置文件认识;通过梳理,我们对MyBatis的使用步骤有了更全面的了解,这进一步提高了我们使用MyBatis的能力。今天我想继续梳理MyBatis这个框架,因为我们了解的,仅仅是冰山的一角。MyBatis中还有很多其他实用的知识点和好的设计思想值得我们深究。那今天就一起研究一下MyBatis配置文件中的plugins元素吧。

1 plugins元素的定义及解析

上篇文章——《MyBatis是纸老虎吗?(三)》——有提到过这个元素。这个元素的作用就是允许开发者指定一个插件,这个插件可以在映射语句执行过程中的某一点进行拦截,然后做一些特殊的处理,比如数据分页、操作日志增强、sql性能监控等。那如何定义一个插件呢?很简单,只需实现org.apache.ibatis.plugin.Interceptor接口即可。下面是一个自定义插件的示例:

@Intercepts({
  @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class ExamplePlugin implements Interceptor {

    private Properties properties = new Properties();

    public Object intercept(Invocation invocation) throws Throwable {
        // implement pre processing if need
        Object returnObject = invocation.proceed();
        // implement post processing if need
        return returnObject;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

}

这个拦截器中的intercept()方法并未做任何处理,只是调用了Invocation对象上的proceed()方法,并将该方法的执行结果返回给上级调用者。梳理到这里,我想看一下Interceptor接口的源码,如下所示:

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;
  // default xxxx,如果没有没记错的话,这是 jdk1.8的新特性
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  default void setProperties(Properties properties) {
    // NOP
  }
}

由此源码,我们可以知道Interceptor,拦截器,是一个接口,其中仅有一个名为intercept的方法,因此实现该接口的类一般都要对这个方法进行实现。上面展示的自定义拦截器就对这个方法进行了实现。注意:这个方法会接收一个Invocation类型的参数,该类的源码如下所示:

public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

梳理到这里,我非常想知道是这个自定义拦截器要怎样用。想必诸位都已先我一步知道了这个问题的答案:直接在MyBatis的配置文件config.xml中新增plugins配置项。具体代码如下所示:

<plugins>
    <plugin interceptor="包名.插件类名"></plugin>
</plugins>

进行到这里,所有的前期准备工作就完成了。下面就一起看一下这个元素的解析过程吧!通过上篇文章我们知道XMLConfigBuilder类的parse()方法开启了执行流程,其中执行解析工作的核心是与其同属一类的parseConfiguration()方法。该方法会对MyBatis配置文件中的元素按照既定顺序逐个解析。这些元素的解析顺序为:properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、environments、databaseIdProvider、typeHandlers、mappers。上节我们一起梳理了properties、settings、typeAliases、environments四个元素的解析过程,这节就详细梳理一下plugins元素的解析过程。解析plugins元素的方法的名为pluginsElement(),其源码如下所示:

private void pluginsElement(XNode context) throws Exception {
  if (context != null) {
    for (XNode child : context.getChildren()) {
      String interceptor = child.getStringAttribute("interceptor");
      Properties properties = child.getChildrenAsProperties();
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor()
          .newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

该方法被调用时的状态如下图所示(注意截图中context参数的内容,就是MyBatis配置文件中的plugins标签下的内容):

借助这段运行时状态图,让我们一起分析一下这段代码的执行逻辑:1.拿到context参数所代表的plugins元素下的所有plugin节点,并遍历这些节点,然后执行下述步骤;2.拿到plugin节点上interceptor属性的值,这个值就是我们自定义的拦截器的包全名+类名;3.解析plugin元素下的子元素,并将其包装为Properties对象;4.解析第二步拿到的数据,然后加载相应的类,并实例化一个对象出来(注意:这里用到了反射);5.将第三步解析出来的Properties对象设置到第四步创建的Interceptor对象上;6.将第四步创建的Interceptor对象设置到Configuration对象的interceptorChain属性上(这个操作是通过调用Configuration类上的addInterceptor()方法完成的。这里还有一点需要注意:interceptorChain的类型为InterceptorChain,这让我想到了责任链及Spring的AOP和事务,有兴趣的可以翻看一下我之前梳理的与这两个主题有关的系列文章《Spring AOP系列》、《Spring事务系列》)。

关于上述解析步骤,个人觉得有以下几点需要注意:

  1. 上述第二步和第五步中都提到了Properties,为什么我们可以在plugin元素中使用property标签呢?为什么我们可以将这些值设置到Interceptor类型的对象上呢?这两个问题很好回答。关于第一个问题:因为MyBatis支持,如若不然,plugins元素的解析逻辑中不会出现Properties properties = child.getChildrenAsProperties()这样一行代码。那MyBatis是怎么支持的呢?这个就要看MyBatis配置文件的dtd约束文件了,先看下面这段从mybatis-3-config.dtd文件中摘抄出来的代码:<!ELEMENT plugin (property*)>。这段代码的大致意思就是说在plugin元素下可以有零个或多个property标签(具体参照下图“mybatis dtd文件关于plugin的定义”)。关于第二个问题:根据前面列出的源码,不难发现Interceptor源码中有一个default修饰的setProperties()方法,该方法返回值为void类型,默认不做任何处理。前面自定义的拦截器实现了这个方法。正因为Interceptor中有这样一个方法,所以解析代码中才有这样一句:interceptorInstance.setProperties(properties)。(也就是上面描述中的第五步)
  2. 上述第二步调用XMLConfigBuilder类的父类BaseBuilder类resolveClass()方法去解析我们在plugin元素中指定的interceptor属性值(包全名+拦截器名),该方法会继续调用BaseBuilder类中的resolveAlias()方法,这个方法会继续调用TypeAliasRegistry对象的resolveAlias()方法,这个方法的源码在上篇文章中已经展示过,这里就不再啰嗦,有兴趣可以翻看源码或者翻阅上篇文章。这段代码会直接将传递进来的string参数转为小写,然后从typeAliases中查找这个string参数代表的key是否存在,如果存在,则直接返回其对应的Class<?>类型的值,如果不存在,则直接使用Resources加载这个类。plugins的解析最终走的就是这一步

mybatis dtd文件关于plugin的定义

2 关于InterceptorChain的介绍

上小节的第六步中提到解析出来的Interceptor的对象会被设置到Configuration对象中的interceptorChain属性上。这个属性的实际类型为InterceptorChain。该类的源码为:

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<>();
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

这个类中定义了一个List类型的变量interceptors,其持有的类型为Interceptor,所以第六步调用Configuration类上的addInterceptor()方法,最终实际上调用的就是这个类上的addInterceptor()方法向interceptors变量中添加数据。这里面的pluginAll()方法是最终调度的入口

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/471754.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux网线正常,但没有网络,ifconfig没有ip地址

ubuntu 22.04环境&#xff1a; 今天正在用着好好的&#xff0c;不知道为什么突然没有网络了&#xff0c;网线灯也不亮&#xff0c;ifconfig只有lo回环地址。 因为装的双系统&#xff0c;切换到windows环境发现网络是正常的。 使用-a&#xff1a; 使用各种方式比如下面的命令…

大模型应用开发-虚拟人-AI刘能、AI李宏伟

简介 本案例通过python编程调用智谱的大模型接口,以及很简单的prompt设计,实现了用大语言模型模拟一个人物来和我们对话,前端HTML代码是用大语言模型生成的(原因:我根本不会写前端啊~~),本教程适合所有对大模型应用开发感兴趣的初学者,这是个非常有趣的案例。 读完本…

excel 破解 保护工作簿及保护工作表

excel 破解 保护工作簿及保护工作表 对于这种 保护工作簿及保护工作表 不知道密码时&#xff0c;可以使用以下方法破解 保护工作簿破解 打开受保存的excel 右键点击sheet名称 —> 查看代码 复制以下代码&#xff0c;粘贴到代码区域 Sub 工作簿密码破解() ActiveWorkbook.…

C语言例:(m=a==b)||(n=a==b);求解m,n的值

题目&#xff1a;设int a0,b0,m0,n0;执行语句(mab)||(nab);求解m,n的值。 #include<stdio.h> int main(void) {int a0,b0,m0,n0;(mab)||(nab);printf("m%d\n",m);printf("n%d\n",n);return 0; } 优先级: () 优先 优先 a b -->为真&am…

Python元组:不可变的序列

文章目录 一、元组1.创建元组2.访问元组中的元素3.修改元组4.删除元组 二、运算符1.加法运算符2.乘法运算符3.in运算符4.not in运算符 三、元组内置方法1.len()2.max()3.min()4.tuple()4.1 将列表转换成元组4.2 将字符串转换成元组4.3 将集合转换成元组 三、总结 一、元组 在P…

【5G NB-IoT NTN】3GPP R17 NB-IoT NTN介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

10000字!一文学会SQL数据分析

文章来源于山有木兮 原文链接&#xff1a;https://edu.cda.cn/goods/show/3412?targetId5695&preview0 第1节 SQL简介与基础知识 做数据分析的&#xff0c;为什么要写SQL&#xff1f; 没有数据的情况下&#xff0c;我们分析数据就像是巧妇难为无米之炊。因此&#xff0c…

Netty学习——源码篇3 服务端Bootstrap(一) 备份

1 介绍 在分析客户端的代码中&#xff0c;已经对Bootstrap启动Netty有了一个大致的认识&#xff0c;接下来在分析服务端时&#xff0c;就会相对简单。先看一下服务端简单的启动代码。 public class ChatServer {public void start(int port) throws Exception{NioEventLoopGro…

解锁鸿蒙小程序开发新姿势

如今&#xff0c;鸿蒙开发日益受到广大开发者的关注&#xff0c;而小程序开发也早已成为互联网领域的热门话题。那么&#xff0c;我们不禁要问&#xff1a;是否有可能将这两者融为一体&#xff0c;将小程序开发的便捷与高效带入鸿蒙生态中呢&#xff1f;本文将首先带你回顾小程…

SpringCloud alibaba入门简介

SpringCloud alibaba入门简介 1、简介 SpringCloud alibaba官网&#xff1a;SpringCloudAlibaba | Spring Cloud Alibaba (aliyun.com) Spring官网&#xff1a;Spring Cloud Alibaba GitHub中文文档&#xff1a;spring-cloud-alibaba/README-zh.md at 2022.x alibaba/spri…

数据库基本介绍及编译安装mysql

目录 数据库介绍 数据库类型 数据库管理系统&#xff08;DBMS&#xff09; 数据库系统 DBMS的工作模式 关系型数据库的优缺点 编译安装mysql 数据库介绍 数据&#xff1a;描述事物的的符号纪录称为数据&#xff08;Data&#xff09; 表&#xff1a;以行和列的形式组成…

公众号怎么更换主体

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;1.可合并多个公众号的粉丝、文章&#xff0c;打造超级大V2.可变更公众号主体&#xff0c;更改公众号名称&#xff0c;变更公众号类型——订阅号、服务号随意切换3.可以增加留言功能4.个人订阅号可迁移到企业名…

零知识玩转AVH(8)—— 门槛任务(3)所遇错误及解决(2)

接前一篇文章&#xff1a;零知识玩转AVH&#xff08;7&#xff09;—— 门槛任务&#xff08;2&#xff09;所遇错误及解决&#xff08;1&#xff09; 上一回说到在尝试完成门槛任务 https://github.com/ArmDeveloperEcosystem/Paddle-examples-for-AVH &#xff08;推荐&#…

阿里G6 树状图使用 Iconfont

官网&#xff1a;使用 Iconfont | G6 效果&#xff1a; 完整代码&#xff1a;index.html: <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width…

Python矩阵计算

文章目录 求积求逆最小二乘法特征值 Python科学计算&#xff1a;数组&#x1f4af;数据生成&#x1f4af;数据交互&#x1f4af;微积分&#x1f4af;插值&#x1f4af;拟合&#x1f4af;FFT&#x1f4af;卷积&#x1f4af;滤波&#x1f4af;统计 求积 矩阵是线性代数的核心对…

开发CodeSys可视化控件

文章目录 背景解决方案HTML5 elementsUsing a Visualization as an Element 背景 目前接公司需求&#xff0c;需要开发一套视觉检测系统&#xff0c;并将其集成到codesys中。 编程端基本是采用之前说得的C接口来实现【CodeSys中调用C语言写的动态库】&#xff0c;但是检测画面…

算法笔记p251队列循环队列

目录 队列循环队列循环队列的定义初始化判空判满入队出队获取队列内元素的个数取队首元素取队尾元素 队列 队列是一种先进先出的数据结构&#xff0c;总是从队尾加入元素&#xff0c;从队首移除元素&#xff0c;满足先进先出的原则。队列的常用操作包括获取队列内元素的个数&a…

打造精美响应式CSS日历:从基础到高级样式

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【09】进阶JavaScript事件循环Promise

一、事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间,可以把这块内存空间简单的理解为进程 每个应用至少有一个进程,进程之间相互独立,即使要通信,也需要双方同意。 何为线程? 有了进程后,就可以运行程序的代码了。 运行代码的「人」称之…

Makefile的基本知识

文章目录 一、使用Makefile 的引入1.GCC的编译流程2.Makefile的引入 二、Makefile的语法规则三、Makefile中的变量1.全局变量2.赋值符“”&#xff0c;“&#xff1a;”&#xff0c;“&#xff1f;”区别 四、Makefile中的自动化变量四、Makefile中伪目标五、Makefile中条件判断…