Mybatis源码解析(八):插件机制

news2024/12/24 3:52:23

Mybatis源码系列文章

手写源码(了解源码整体流程及重要组件)

Mybatis源码解析(一):环境搭建

Mybatis源码解析(二):全局配置文件的解析

Mybatis源码解析(三):映射配置文件的解析

Mybatis源码解析(四):sql语句及#{}、${}的解析

Mybatis源码解析(五):SqlSession会话的创建

Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码解析(六):查询数据库主流程

Mybatis源码解析(七):Mapper代理原理

Mybatis源码解析(八):插件机制


目录

  • 前言
  • 一、Mybatis插件介绍
  • 二、自定义插件
    • 1、自定义插件注解和接口
    • 2、代码示例
  • 三、源码分析-插件
    • 1、加载解析拦截器
    • 2、拦截器包装四大对象
    • 3、代理对象生成时序图
  • 四、四大对象拦截位置
  • 总结


前言

什么是Mybatis插件?有什么作用?

  • 一般开源框架都会提供扩展点,让开发者自行扩展,从而完成逻辑的增强
  • 基于插件机制可以实现了很多有用的功能,比如说分页,字段加密,监控等功能,这种通用的功能,就如同AOP一样
  • 通过Mybatis插件可以实现对框架的扩展,来实现自定义功能,并且对于用户是无感知的

一、Mybatis插件介绍

  • Mybatis插件本质上来说就是一个拦截器,它体现了JDK动态代理责任链设计模式的综合运用
  • Mybatis中针对四大组件提供了扩展机制,这四个组件分别是:
    在这里插入图片描述
  • 允许拦截的方法如下:
    • Executor 【SQL执行器】【update,query,commit,rollback】
    • StatementHandler 【Sql语法构建器对象】【prepare,parameterize,batch,update,query等】
    • ParameterHandler 【参数处理器】【getParameterObject,setParameters等】
    • ResultSetHandler 【结果集处理器】【handleResultSets,handleOuputParameters等】

二、自定义插件

1、自定义插件注解和接口

注解

  • @Intercepts注解-标记为拦截器类(插件类)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}
  • @Signature注解-标记拦截器拦截哪个类的哪个方法
  • 可以设置多个拦截器,所有可以配置多个@Signature
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  Class<?> type();

  String method();

  Class<?>[] args();
}

接口

  • 拦截后要干什么就需要实现 Intercetor#intercept 方法
public interface Interceptor {

  // 真正方法被拦截执行的逻辑
  Object intercept(Invocation invocation) throws Throwable;

  // 生成目标对象的代理对象
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  // 可以拦截器设置一些属性
  default void setProperties(Properties properties) {

  }
}

2、代码示例

  • 使用@Intercepts注解拦截四大对象之一的sql语句处理器的prepare方法
  • 执行流程
    • setProperties():先执行 xml中配置此插件时候可以添加自定义参数,这里可以获取
    • plugin():再执行 如果确认正在创建的对象为拦截对象,则再套一次,jdk动态代理
    • intercept():最后执行 上一步动态代理的invoke方法就是执行此方法,拦截方法后的增强逻辑
  • invocation对象包含拦截的对象、拦截的对象方法、方法参数
  • 本拦截器是拦截StatementHandler对象,所有invocation.getTarget()即使此对象
  • invocation.proceed():放行;执行后面的拦截器或对应的拦截方法,否则将返回,不再往后执行
@Intercepts({
    @Signature(type = StatementHandler.class,
               method = "prepare",
               args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {

  /**
   * 拦截方法:每次执行目标方法时,都会进入到intercept方法中
   * @param invocation :多个参数的封装类
   */
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // 增强逻辑:将执行的sql进行记录(打印)
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = statementHandler.getBoundSql();
    String sql = boundSql.getSql();

    System.out.println("拦截方法,记录Sql:" + sql);

    return invocation.proceed();
  }

  /**
   * 将目标对象生成代理对象,添加到拦截器链中
   * @param target :目标对象
   */
  @Override
  public Object plugin(Object target) {
    // wrap 将目标对象,基于JDK动态代理生成代理对象
    return Plugin.wrap(target,this);
  }

  /**
   * 设置属性
   */
  @Override
  public void setProperties(Properties properties) {
    System.out.println("插件配置的初始化参数:" + properties);

  }
}

将插件注册到全局配置文件中

<configuration>
	...
    <plugins>
        <plugin interceptor="com.xc.interceptor.MyPlugin">
            <property name="username" value="zhangsan"/>
        </plugin>
    </plugins>
    ...
</configuration>

执行结果

已连接到目标 VM, 地址: ''127.0.0.1:51955',传输: '套接字''
插件配置的初始化参数:{username=zhangsan}
拦截方法,记录SqlSELECT id,username FROM  user WHERE id = ?
与目标 VM 断开连接, 地址为: ''127.0.0.1:51955',传输: '套接字''

进程已结束,退出代码0

核心思想

  • 使用JDK动态代理的方式,对这四个对象进行包装增强
  • 创建一个类实现Mybatis的拦截器接口,并且加入到拦截器链(拦截器集合)
  • 在创建四大对象的时候,不直接返回,而是遍历拦截器链,把每一个拦截器都作用于四大对象中
  • 这么一来,Mybatis创建的四大对象其实都是代理对象,都是被包装过的

三、源码分析-插件

1、加载解析拦截器

  • 插件配置在核心配置文件xml中,所以插件的解析也在这里
  • Mybatis源码解析(二):全局配置文件的解析,进入<configuration>标签的子标签<plugins>的解析

进入<plugins>标签的解析方法

  • <plugins>可以配置多个拦截器<plugin>,这里就是遍历多个<plugin>标签
  • child.getStringAttribute(“interceptor”):获取到的拦截器类的全限定类路径(“com.xc.interceptor.MyPlugin”),反射获取自定义拦截器对象
  • interceptorInstance.setProperties:这里就是调用自定义拦截器的属性初始化方法,xml中配置属性,可以在自定义拦截器中获取
  • addInterceptor:添加到拦截器链(拦截器集合)
private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      // 获取拦截器
      String interceptor = child.getStringAttribute("interceptor");
      // 获取配置的Properties属性
      Properties properties = child.getChildrenAsProperties();
      // 根据配置文件中配置的插件类的全限定名 进行反射初始化
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
      // 将属性添加到Intercepetor对象
      interceptorInstance.setProperties(properties);
      // 添加到配置类的InterceptorChain属性,InterceptorChain类维护了一个List<Interceptor>
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

添加到拦截器链

  • InterceptorChain:拦截器链对象,核心属性就是interceptors-拦截器对象集合
public void addInterceptor(Interceptor interceptor) {
  interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();
  ...
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  ...
}

2、拦截器包装四大对象

  • 上面说到拦截器就是为四大对象创建代理对象

查看执行器对象的创建的源码

  • 先创建批量、重用或简单执行器
  • 默认开启缓存,则执行器外添加一层缓存执行器(装饰者模式)
  • 上面说到,自定义拦截器会添加到拦截器链interceptorChain中,查看pluginAll
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  // 如果允许缓存,会通过CachingExecutor 去代理一层
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  // 拦截器插件
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

查看拦截器链的pluginAll方法

  • 遍历拦截器链的自定义拦截器
  • 调用拦截器的plugin方法,这个方法就是自己创建自定义拦截器中的plugin方法(创建代理对象)
    在这里插入图片描述
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

查看Plugin的wrap方法

  • Map对象signatureMap
    • key:拦截的四大对象对应的Class对象
    • value:拦截的四大对象的方法
  • getAllInterfaces方法:判断目标类对象是否是拦截的对象,是,则添加到interfaces中
  • 判断interfaces,不为0,表示拦截器对象与目标对象一致,则通过JDK动态代理创建代理类返回
  • 第三个参数new Plugin(),肯定是InvocationHandler的实现类,invoke方法则是增强方法
public static Object wrap(Object target, Interceptor interceptor) {
  // 1.解析该拦截器所拦截的所有接口及对应拦截接口的方法
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
  // 2.获取目标对象实现的所有被拦截的接口
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  // 3.目标对象有实现被拦截的接口,生成代理对象并返回
  if (interfaces.length > 0) {
    // 通过JDK动态代理的方式实现
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  // 目标对象没有实现被拦截的接口,直接返回原对象
  return target;
}

查看Plugin的invoke方法

  • interceptor.intercept:这里即时调用自己创建的拦截器的实现方法
    在这里插入图片描述
  • Invocation:目标对象、方法、参数则是这里new对象传入自定义拦截器的intercept方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 获取被拦截的方法 key:拦截的组件对象 value:拦截的组件对象的方法
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    // 如果当前执行的方法属于拦截方法,那就执行代理对象的方法intercept
    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);
  }
}

查看自定义插件intercept方法的invocation.proceed()

  • method:目标方法也是拦截方法
  • 此时拦截方法的增强逻辑已完成,接下来执行目标方法的原始逻辑
public Object proceed() throws InvocationTargetException, IllegalAccessException {
  return method.invoke(target, args);
}

3、代理对象生成时序图

  • 同一个组件对象的同一个方法可以被多个拦截器进行拦截
  • 配置在最前面的拦截器最先被代理,但是执行的时候却是最外层的先执行
  • 执行流程:
    • 假如依次定义了三个插件: 插件1、插件2、插件3
    • 包装代理类顺序:插件1、插件2、插件3
    • 执行代理对象的invoke方法顺序:插件3、插件2、插件1
      在这里插入图片描述

在这里插入图片描述

四、四大对象拦截位置

  • 创建执行器对象方法

在这里插入图片描述

  • 创建参数处理器对象方法

在这里插入图片描述

  • 创建返回值处理器对象方法

在这里插入图片描述

  • 创建sql语句执行器对象方法

在这里插入图片描述


总结

  • Mybatis插件就是为四大对象创建代理类
  • 拦截四大对象中的方法,invoke添加增强的内容,之后再执行四大对象的原始方法

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

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

相关文章

flex布局列表页(一行内容比较多,长度比较长)

一、Flex 布局是什么&#xff1f; Flex 是 Flexible Box 的缩写&#xff0c;意为"弹性布局"&#xff0c;用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局。 二、基本概念 采用 Flex 布局的元素&#xff0c;称为 Flex 容器&#xff08;flex co…

Java开发必须掌握的运维知识 (九)-- Docker容器监控信息可视化仪表:Grafana

一、Grafana 是用来干什么的&#xff1f; Grafana 是一个监控仪表系统&#xff0c;它是由 Grafana Labs 公司开源的的一个系统监测 (System Monitoring) 工具。它可以大大帮助你简化监控的复杂度&#xff0c;你只需要提供你需要监控的数据&#xff0c;它就可以帮你生成各种可视…

jdk11新特性——JShell交互性工具

目录一、jshell概述二、jshell位置三、jshell示例3.1、示例一&#xff08;声明变量并赋值&#xff09;3.2、示例二&#xff08;输出打印内容&#xff09;3.3、示例三&#xff08;帮助命令&#xff09;一、jshell概述 java9引入了jshell这个交互性工具&#xff0c;让Java也可以…

BUUCTF Misc 黑客帝国 [MRCTF2020]你能看懂音符吗 [HBNIS2018]caesar [HBNIS2018]低个头

黑客帝国 下载文件 一长串16进制&#xff0c;复制到在线16进制转文本 很明显是一个RAR文件&#xff0c;使用脚本将16进制转换成文件 import binasciihex_data这里填十六进制数据 outopen(res.rar,wb) out.write(binascii.unhexlify(hex_data)) out.close() 需要密码&#xff…

[附源码]计算机毕业设计校园疫情防范管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

# 智慧社区管理系统-核心业务管理-03投诉信息

一 后端 1:entity package com.woniu.community.entity;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;Data AllArgsConstructor NoArgsConstructor public class Complaint {private int id;private int comId;private String c…

多功能跑步机外观及结构设计

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1背景及意义 1 1.2国内外发展情况 2 1.3研究主要内容 3 第2章 多功能跑步机结构设计 5 2.1 传动机构设计 5 2.1.1 设计步骤及参数选择 5 2.1.2多楔带带轮结构和尺寸选择 8 2.2 电动机类型与参数的选择 10 2.2.1类型功率选择 10 2.2.2…

连连看核心算法与基本思想(附全部项目代码链接与代码详细注释)

文章目录0.说明1.基本要求2.思路分析(加入核心代码)2.1 游戏初始化局面2.2 两点是否可连2.3 游戏是否结束2.4 判断死锁3.注意事项与全部代码0.说明 对于数据结构和算法&#xff0c;我并不是很精通&#xff08;真的很一般&#xff09;&#xff0c;因此在这里只是做一个自己的简…

以太网 TCP协议(数据交互过程、窗口机制)

2.7.1 以太网 TCP协议&#xff08;数据交互过程、窗口机制&#xff09; 环境介绍&#xff1a; 1、客户端访问FTP服务器进行下载文件&#xff0c;由于FTP是基于TCP协议进行工作的&#xff0c;所以客户端在访问FTP服务器时必然会进行建立TCP连接。 2、通过在交换机上对任意端口进…

Bootstrap5 网格系统

Bootstrap 提供了一套响应式、移动设备优先的流式网格系统&#xff0c;随着屏幕或视口&#xff08;viewport&#xff09;尺寸的增加&#xff0c;系统会自动分为最多 12 列。 我们也可以根据自己的需要&#xff0c;定义列数&#xff1a; 111111111111444486612 Bootstrap 5 的…

Java项目:ssm客户关系管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 客户关系管理系统主要功能包括&#xff1a; 系统管理&#xff1a; 用户管理 日志管理 权限管理 角色管理 系统信息 客户管理 我的客户 联系跟进…

音视频学习(十三)——flv详解

简介 全称FLASHVIDEO&#xff0c;是一种新的视频格式&#xff0c;主要的特点是文件小、加载速度快。 结构 flv的结构相对简单&#xff0c;可以通过下图来初步了解其组成&#xff1a; flv flv header(9字节) flv bodyflv header Signature(3字节) Version(1字节) Flags(…

rocketmq消息过滤

1、目的 消费者订阅了某个主题后&#xff0c;Apache RocketMQ 会将该主题中的所有消息投递给消费者。若消费者只需要关注部分消息&#xff0c;可通过设置过滤条件在 Apache RocketMQ 服务端进行过滤&#xff0c;只获取到需要关注的消息子集&#xff0c;避免接收到大量无效的消…

51单片机学习笔记5 流水灯实现及蜂鸣器控制

51单片机学习笔记5 流水灯实现及蜂鸣器控制一、流水灯1. 硬件电路2. 代码实现&#xff08;1&#xff09; 点亮一个LED的基本操作&#xff08;2&#xff09; 使用算术左移实现流水灯&#xff08;3&#xff09; 使用库文件左移函数二、蜂鸣器1. 蜂鸣器介绍2. 硬件电路原理图ULN20…

唯愿山城无恙-烟火寻常--爱摸鱼的美工(五)

-----------作者&#xff1a;天涯小Y 非常时期的非常生活&#xff01; 今天是承上启下的一天 无论好坏&#xff0c;本月都收官了 洗去一身的疲惫 大脑放空&#xff0c;搓澡 热衷搓澡的程度&#xff0c;简直不像南方人 收拾一下,迎接新的开始 原本一年之中&#xff0c;我最爱几…

搜索与图论-DFS

文章目录一、DFS1. DFS 简介2. DFS 的实现步骤3. DFS 实际演示二、DFS 例题——排列数字具体实现1. 样例演示2. 实现思路3. 代码注解4. 实现代码三、DFS 例题—— n-皇后问题&#xff08;经典&#xff09;具体实现——按行进行枚举1. 样例演示2. 实现思路3. 代码注解4. 实现代码…

微服务框架 SpringCloud微服务架构 9 初识 Docker 9.4 Docker 的安装

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构9 初识 Docker9.4 Docker 的安装9.4.1 安装Docker9.4.2 启动Docker9.4.3 配…

verilog练习——时序逻辑

目录 VL21 根据状态转移表实现时序电路 VL22 根据状态转移图实现时序电路 VL23 ROM的简单实现 VL24 边沿检测 VL21 根据状态转移表实现时序电路 题目分析&#xff1a; 1、使用三段式状态机&#xff0c;实现更为方便和简洁。 2、三段式和&#xff08;一段式、二段式&#x…

C++GUI之wxWidgets(2)-hello,world

目录wxWidgets的头文件事件main入口函数状态栏事件处理程序wxWidgets的头文件 首先必须包含wxWidgets的头文件。 1、可以在一个文件一个文件的基础上完成(如wx/window.h) 2、使用一个全局包含(wx/wx.h)&#xff0c;其中包括大多数常用的头文件(虽然不是所有的头文件&#xff0…

单变量微积分重点(1)

1.单调有界定理 若数列递增有上界&#xff0c;则数列收敛&#xff08;递减同样&#xff09; 2.海涅定理&#xff08;归结原则&#xff09; 说明&#xff1a;对于任何的属于空心邻域的数列&#xff0c;而且这些数列的极限都是x0. 3.两个重要极限&#xff1a; 4.11个重要极限 导…