MyBatis是纸老虎吗?(四)

news2025/2/24 15:24:10

在《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.coloradmin.cn/o/1529158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

threejs之贴图原理

// 导入threejs import * as THREE from "three"; // 导入轨道控制器 import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";// 创建场景 const scene new THREE.Scene();// 创建相机 const camera new THREE.PerspectiveCame…

使用Java JDBC连接数据库

在Java应用程序中&#xff0c;与数据库交互是一个常见的任务。Java数据库连接&#xff08;JDBC&#xff09;是一种用于在Java应用程序和数据库之间建立连接并执行SQL查询的标准API。通过JDBC&#xff0c;您可以轻松地执行各种数据库操作&#xff0c;如插入、更新、删除和查询数…

有没搞错!花了大价钱的激光孔设计性能竟然不如普通通孔?

高速先生成员--黄刚 老话说得好&#xff0c;一分耕耘一分收获&#xff0c;又或者另外一句&#xff0c;有什么付出就会得到多少收获。我们都不会去怀疑这些话的正确性。但是把这两句话用到PCB领域中&#xff0c;用了好的加工工艺后&#xff0c;PCB板的性能就一定会比用普通工艺要…

视频素材免费下载素材库哪里有?推荐8个高清无水印素材网

在这个数字化时代&#xff0c;无论是专业的内容创作者还是日常的社交媒体使用者&#xff0c;我们都会寻找高质量的素材来丰富我们的作品或帖子。从令人震撼的摄影作品到高分辨率的视频素材&#xff0c;再到生动的GIF和必需的设计元素&#xff0c;素材的需求无处不在。 视频素材…

XXL-JOB完全开发手册(一篇学会XXL-JOB所有知识点)

目录 1、什么是XXL-JOB 1.1、XXL-JOB简介 1.2、XXL-JOB构成 调度模块&#xff08;调度中心&#xff09;&#xff1a; 执行模块&#xff08;执行器&#xff09;&#xff1a; 任务&#xff1a; 1.3、XXL-JOB总结 ​编辑 2、XXL-JOB原理 2.1、执行器的注册和发现 2.2、调度中心调用…

基于华为ensp的企业网络规划(新版)

第一章 项目概述 1.1 项目总体描述 假设某大型公司总部在北京、在重庆设置分部&#xff0c;总部和分部均有研发部、市场部、财务部等部门&#xff0c;现在要求进行网络规划与设计&#xff0c;实现分部和总部能够进行网络连通。为了保证数据安全&#xff0c;在总部和分部之间可…

教师怎么发成绩才不会被投诉

在当今社会&#xff0c;教育的重要性日益凸显&#xff0c;而学生的成绩作为衡量教育效果的重要指标之一&#xff0c;备受家长和学生的关注。然而&#xff0c;教师在发布成绩时&#xff0c;稍有不慎就可能引发家长的不满和投诉。那么&#xff0c;教师该如何发成绩才能避免被投诉…

【Spring Cloud Gateway】路由配置uri三种方式及区别

websocket配置方式 ws:// 或 wss:// 开头的 URI&#xff0c;表示配置的是支持 Websocket 协议的目标地址。 这种方式适用于需要与客户端建立长连接、实现双向通信的场景&#xff0c;比如实时消息推送、即时聊天等。 使用 Websocket 配置方式可以让 Spring Cloud Gateway 能够…

【物联网】Modbus 协议及应用

Modbus 协议简介 QingHub设计器在设计物联网数据采集时不可避免的需要针对Modbus协议的设备做相关数据采集&#xff0c;这里就我们的实际项目经验分享Modbus协议 简介 Modbus由MODICON公司于1979年开发&#xff0c;是一种工业现场总线协议标准。1996年施耐德公司推出基于以太…

虚拟机VMware上 centos7 的网络配置

第一步&#xff1a;权限的切换 由普通用户切换到管理者/超级用户 用户名为&#xff1a;root 密码为&#xff1a;自己安装 linux 时第一次设置的密码 su -root管理者/超级用户的命令提示符是“#”&#xff0c;普通用户的命令提示符是“$”。当看到你的命令提示符为“$”时&…

《世界之外》玩家闹上315,乙游打响维权大战

315维权微博的评论区&#xff0c;竟然被举报网易的玩家占领了。 玩家举报网易乙游《世界之外》虚假宣传侵害消费者权益&#xff0c;在游戏中设置排行榜和专属商店将玩家分为三六九等&#xff0c;诱导玩家消费氪金&#xff0c;强烈要求网易打开退款通道。 目前大批玩家举报的举…

如何在三个简单步骤中为对象检测标注图像

初始通过彻底清洗和处理原始图像数据来奠定有效对象检测注释的基础。选择适合的工具、方法和清晰的注释过程指南来建立注释工作空间。通过在图像中划定对象并附上类别标签来执行注释&#xff0c;随后进行细致的核验&#xff0c;以确保数据集的精确性和完整性。 图像注释是计算…

klipper源码分析之simulavr测试

分析Klipper源码&#xff0c;有时需要结合下位机一起分析&#xff0c;这样才能更加全面的了解Klipper的工作原理。如果手头上有打印机主板&#xff0c;电脑当做上位机运行Klipper&#xff0c;这样是比较方便。如果手头上没有打印机主板&#xff0c;可以用simulavr模拟AVR下位机…

蓝桥杯备赛_python_DFS搜索算法_刷题学习笔记

1.是什么 沿着一条路径一直搜索下去&#xff0c;在无法搜索时&#xff0c;回退到刚刚访问过的节点。并且每个节点只能访问一次。本质上是持续搜索&#xff0c;遍历了所有可能的情况&#xff0c;必然能得到解。 流程是一个树的形式&#xff0c;每次一条路走到黑。 目的主要是达到…

外包2月,技术退步惊现!大专生逆袭大厂,全靠这份神秘资料!

大家好&#xff0c;我是一名大专生&#xff0c;19年通过校招进入湖南某软件公司&#xff0c;从事功能测试工作已近4年。今年8月&#xff0c;我意识到长期舒适的环境让我变得不思进取&#xff0c;技术停滞不前&#xff0c;甚至因此失去了谈了2年的女朋友。我下定决心&#xff0c…

数据容器-list-Python

师从黑马程序员 列表的定义语法 注&#xff1a;列表可以一次存储多个数据&#xff0c;且可以为不同的数据类型&#xff0c;支持嵌套 my_list["itheima","chengxuyuan","python"] print(my_list) print(type(my_list))#元素类型不受限 my_list[&…

明微电源适配器AC-DC降压型SSR/Buck/Buck-Boost驱动芯片SM7012

电源适配器是一种将市电转换成直流电的装置&#xff0c;常用于为电子设备提供稳定的电源。AC-DC降压型SSR&#xff08;固态继电器&#xff09;、Buck、Buck-Boost是三种不同的电源转换技术。 1. SSR&#xff08;固态继电器&#xff09;&#xff1a;SSR是一种电子开关&#xff…

Linux之shell文本编辑工具sed

华子目录 sed概念工作原理 基本语法格式参数定位符操作 输出文本范例文本定址符和操作符连用搭配正则使用规定语法 文本替换范例文件格式示例&示例\u大写字母\L小写字母\b确定单词边界 配置实例使用替换实现删除 删除文本示例 插入文本格式示例 注意 sed概念 sed是一个非交…

libVLC windows开发环境搭建

1.简介 LibVLC是一个强大的开源库&#xff0c;它构成了VLC媒体播放器的核心部分。 LibVLC提供了一系列的功能接口&#xff0c;使得VLC能够处理流媒体的接入、音频和视频输出、插件管理以及线程系统等核心任务。 跨平台性&#xff1a;VLC作为一个跨平台的多媒体播放器&#x…

源码部署LAMP架构

LAMP 文章目录 LAMP1. lamp简介2. web服务器工作流程2.1 cgi与fastcgi2.2 httpd与php结合的方式2.3 web工作流程 3. LAMP平台构建3.1 安装httpd3.2 安装mysql3.3 安装php3.4 验证 1. lamp简介 有了前面学习的知识的铺垫&#xff0c;今天可以来学习下第一个常用的web架构了。 …