当前位置: 首页 > news >正文

Mybatis插件开发及执行原理

mybatis源码下载

https://github.com/mybatis/mybatis-3,本文分析源码版本3.4.5

mybatis启动大致流程

在看这篇文章前,建议查看我另一篇文章,以了解框架启动的流程和框架中一些重要对象:https://blog.csdn.net/Aqu415/article/details/79049739

可以被插件代理的类

Mybatis中可以被插件的类分为4种,分别是Executor、StatementHandler、ParameterHandler、ResultSetHandler。

InterceptorChain

这里说一个在插件开发过程中比较重要的类,这个类会缓存系统中所有的插件类(一个Configuration对应一个InterceptorChain);InterceptorChain中缓存的插件会在框架启动时伴随解析配置文件中plugins节点完成
InterceptorChain代码如下:

package org.apache.ibatis.plugin;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;/*** @author Clinton Begin*/
public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();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);}
}

我们看看哪些地方调用了该方法:
在这里插入图片描述

调用这个方法的地方有如下(可见上面说的四大对象都是由Configuration这个类创建的):

1、org.apache.ibatis.session.Configuration#newExecutor
2、org.apache.ibatis.session.Configuration#newStatementHandler
3、org.apache.ibatis.session.Configuration#newParameterHandler
4、org.apache.ibatis.session.Configuration#newResultSetHandler

目标对象被代理的时机

我们挑第一个方法来看看,发现目标对象被代理的时机在对象创建后直接调用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);}if (cacheEnabled) {executor = new CachingExecutor(executor);}// @Aexecutor = (Executor) interceptorChain.pluginAll(executor);return executor;}

@A:调用InterceptorChain对已经创建好的Executor对象进行代理(插件化包装),调用pluginAll方法实则是调用每一个插件的plugin方法

如下是 Executor 的方法定义:


package org.apache.ibatis.executor;import java.sql.SQLException;
import java.util.List;import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;/*** @author Clinton Begin*/
public interface Executor {ResultHandler NO_RESULT_HANDLER = null;int update(MappedStatement ms, Object parameter) throws SQLException;<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;List<BatchResult> flushStatements() throws SQLException;void commit(boolean required) throws SQLException;void rollback(boolean required) throws SQLException;CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);boolean isCached(MappedStatement ms, CacheKey key);void clearLocalCache();void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);Transaction getTransaction();void close(boolean forceRollback);boolean isClosed();void setExecutorWrapper(Executor executor);
}

这个类主要数据库操作相关的顶级接口,如果要在执行数据库操作较早时机对方法进行拦截,可以对Executor进行插件编写,接口里定义的方法都可以被插件拦截到。

插件的编写

需要实现 org.apache.ibatis.plugin.Interceptor接口,编程模式可以参考框架提供的例子类

package org.apache.ibatis.builder;import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;import java.util.Properties;// @B
@Intercepts({})
public class ExamplePlugin implements Interceptor {private Properties properties;@Overridepublic Object intercept(Invocation invocation) throws Throwable {return invocation.proceed();}@Overridepublic Object plugin(Object target) {// @Areturn Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {this.properties = properties;}public Properties getProperties() {return properties;}
}

@A:调用Plugin类生成一个代理类,返回这个代理对象
@B:完善需要拦截的目标类,指定方法和参数类型(作用后面分析)

工具类Plugin

框架提供一个通用生成代理的工具类 org.apache.ibatis.plugin.Plugin,其实我们可以用Plugin的api来包装目标对象也可以采用自定义的方式;采用Plugin是为了后面更方便使用@Intercepts等注解对目标类和目标方法进行过滤,而不是对所有以上四种类型的所有方法进行拦截;
因为在Plugin内部方法拦截时做了特殊过滤,使用起来更方便。

我们来看看Plugin 类

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;import org.apache.ibatis.reflection.ExceptionUtil;/*** @author Clinton Begin*/
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> 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);}}private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      }Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();for (Signature sig : sigs) {Set<Method> methods = signatureMap.get(sig.type());if (methods == null) {methods = new HashSet<Method>();signatureMap.put(sig.type(), methods);}try {Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<Class<?>>();while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}return interfaces.toArray(new Class<?>[interfaces.size()]);}
}

这个类其实兼有两大作用
1、生成代理,代理即目标对象与自定义逻辑间的枢纽拦截器(对应wrap方法)
2、自己充当这个拦截器(基于jdk代理模式)
如果分开成两个类的话,分析起来会更清晰一些

invoke

我们知道基于JDK动态代理的代理对象,会在目标方法前执行其invoke方法;我们分析一下这个方法

  @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// @ASet<Method> 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);}}

@A:这里从缓存里看是否有配置目标类和方法,如果有的话则执行拦截器的intercept方法,也就是插件的intercept方法;
目标类和目标方法是在创建代理对象的时候,通过getSignatureMap返回;其主要是解析插件上的Intercepts注解

插件对真实对象方法拦截时机

1、从以上分析如果有多个插件,那么插件之间会形成上一个插件代理下一个插件的情况;
2、由于生成四大对象时,对目标对象进行了org.apache.ibatis.plugin.InterceptorChain#pluginAll包装,那么框架真正得到的对象就是代理对象,那么其执行方法的时候就是拦截时机

Object intercept(Invocation invocation) throws Throwable;

我们接下来分析一下执行插件的intercept方法时候,传给它的参数具体是什么;其类型是:org.apache.ibatis.plugin.Invocation

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** @author Clinton Begin*/
public class Invocation {// @Aprivate final Object target;
// @Bprivate final Method method;
// @Cprivate 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);}
}

@A:目标对象,即Executor、StatementHandler、ParameterHandler、ResultSetHandler四大类型的真实对象;如果是多层插件代理同一个类型又可能是原始对象,但也有可能是上一个代理对象
@B:方法签名
@C:参数

相关文章:

Mybatis插件开发及执行原理

mybatis源码下载 https://github.com/mybatis/mybatis-3&#xff0c;本文分析源码版本3.4.5 mybatis启动大致流程 在看这篇文章前&#xff0c;建议查看我另一篇文章&#xff0c;以了解框架启动的流程和框架中一些重要对象&#xff1a;https://blog.csdn.net/Aqu415/article/…...

vue父子组件通信,兄弟组件通信

目录 一、父子组件通信 1、子组件通过 props 获取父组件变量和父组件调用子组件中的方法(这两个都是父传子的思想) a:子组件通过 props 获取父组件变量 b:父组件调用子组件中的方法 2、父组件通过ref获取子组件变量和子组件调用父组件的方法&#xff08;这两个都是子传父的…...

大数据技术之Hadoop集群配置

作者简介&#xff1a;大家好我是小唐同学(๑>؂<๑&#xff09;&#xff0c;好久不见&#xff0c;为梦想而努力的小唐又回来了&#xff0c;让我们一起加油&#xff01;&#xff01;&#xff01; 个人主页&#xff1a;小唐同学(๑>؂<๑&#xff09;的博客主页 目前…...

MicroBlaze系列教程(7):AXI_SPI的使用(M25P16)

文章目录 AXI_SPI简介MicroBlaze硬件配置常用函数使用示例波形实测参考资料工程下载本文是Xilinx MicroBlaze系列教程的第7篇文章。 AXI_SPI简介 Xilinx AXI-SPI IP共有两个:一个是标准的AXI_SPI,即4线制SPI,CS、SCLK、MOSI和MISO,另一个是AXI_Quad SPI,支持配置成标准SP…...

使用Python通过拉马努金公式快速求π

使用Python通过拉马努金公式快速求π 一、前言 π是一个数学常数&#xff0c;定义为&#xff1a;圆的周长与直径的比值。 π是一个无理数&#xff0c;也是一个超越数&#xff0c;它的小数部分无限不循环。 π可以用来精确计算圆周长、圆面积、球体积等几何形状的关键值。 有关…...

第六章 使用系统类提供国家语言支持 - 创建自定义语言环境

文章目录第六章 使用系统类提供国家语言支持 - 创建自定义语言环境创建自定义语言环境第六章 使用系统类提供国家语言支持 - 创建自定义语言环境 创建自定义语言环境 此示例将提供一个模板&#xff0c;用于使用自定义表创建自定义语言环境。自定义表将在 EBCDIC&#xff08;美…...

「题解」解决二进制数中1的个数

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练 &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下定决心去做” &#x1f680;&#x1f680;&#x1f680;大家觉不错…...

泛型详解.

1 泛型的引入 问题&#xff1a;我们之前实现过的顺序表&#xff0c;只能保存 int 类型的元素&#xff0c;如果现在需要保存 指向 Person 类型对象的引用的顺序表&#xff0c;请问应该如何解决&#xff1f;如果又需要保存指向 Book 对象类型的引用呢&#xff1f; 之前写的顺序表…...

Vue 3.0 响应性 深入响应性原理 【Vue3 从零开始】

现在是时候深入了&#xff01;Vue 最独特的特性之一&#xff0c;是其非侵入性的响应性系统。数据模型是被代理的 JavaScript 对象。而当你修改它们时&#xff0c;视图会进行更新。这让状态管理非常简单直观&#xff0c;不过理解其工作原理同样重要&#xff0c;这样你可以避开一…...

升级 vue3 常见问题总汇

Ⅰ、前言 虽然 vue3 是没有删除 vue2 的 选项式 API &#xff0c; 但是我们升级vue3 还是需要修改很多问题的下面来看看我们升级常见的一些问题 &#x1f447; 文章目录Ⅰ、前言Ⅱ、解决兼容问题1、路由的创建方式2、路由的方法变化3、升级 vuex 到 4.x4、作用域 插槽语法修改…...

汽车 Automotive > T-BOX GNSS高精定位测试相关知识

参考&#xff1a;https://en.wikipedia.org/wiki/Global_Positioning_SystemGPS和GNSS的关系GPS&#xff08;Global Positioning System&#xff09;&#xff0c;全球定位系统是美国军民两用的导航定位卫星系统&#xff0c;GPS包含双频信号&#xff0c;频点L1、L2和L5GNSS&…...

大数据面试核心101问【大厂超级喜欢这些题】

之前出过《史上最全的大数据开发八股文》这篇文章&#xff0c;同学们都觉得还不错&#xff0c;但是有些同学觉得那个背起来还是有些吃力&#xff0c;于是我再次回顾了自己之前面试所有大厂的一些面试题以及牛客上面的一些面经&#xff0c;然后总结了频率问的最高的101问&#x…...

代码随想录算法训练营第四十八天 | leetcode 121. 买卖股票的最佳时机,122.买卖股票的最佳时机II

代码随想录算法训练营第四十八天 | leetcode 121. 买卖股票的最佳时机&#xff0c;122.买卖股票的最佳时机II121. 买卖股票的最佳时机122.买卖股票的最佳时机II121. 买卖股票的最佳时机 题目&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支…...

RAD 11.3 delphi和C++改进后新增、废弃及优化的功能

RAD 11.3 delphi和C改进后新增和废弃的功能 目录 RAD 11.3 delphi和C改进后新增和废弃的功能 一、版本RAD 11.3 delphi和C改进后新增功能 1、官方视频位置&#xff1a; 2、官方文档的链接位置&#xff1a; 二、版本RAD 11.3 delphi和C改进后废弃的功能 2.1、编译器不再使…...

【C++】引用

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、引用1.1 引用概念1.2 引用特性…...

LPNet for Image Derain

Lightweight Pyramid Networks for Image Deraining前置知识高斯-拉普拉斯金字塔图像中的高频信息和低频信息为什么高斯-拉普拉斯金字塔可以实现去雨&#xff1f;可能性分析网络结构整体结构&#xff1a;子网结构&#xff1a;递归块结构&#xff1a;后续补充代码 前置知识 这…...

【NLP相关】基于现有的预训练模型使用领域语料二次预训练

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…...

使用git进行项目管理--git使用及其常用命令

使用git进行项目管理 文章目录 使用git进行项目管理git使用1.添加用户名字2.添加用户邮箱3.git初始化4.add5.commit6.添加到gitee仓库7.推送到gitee8.切换版本git常用命令git add把指定的文件添加到暂存区中添加所有修改、已删除的文件到暂存区中添加所有修改、已删除、新增的文…...

Mybatis_CRUD使用

目录1 Mybatis简介环境说明:预备知识:1.1 定义1.2 持久化为什么需要持久化服务呢&#xff1f;1.3 持久层1.4 为什么需要Mybatis2 依赖配置3 CRUDnamespaceselect &#xff08;查询用户数据&#xff09;※传值方式&#xff1a;于方法中传值使用Map传值insert &#xff08;插入用…...

JVM的过程内分析和过程间分析有什么区别?

问&#xff1a; 目前所有常见的Java虚拟机对过程间分析的支持都相 当有限&#xff0c;要么借助大规模的方法内联来打通方法间的隔阂&#xff0c;以过程内分析&#xff08;Intra-Procedural Analysis&#xff0c; 只考虑过程内部语句&#xff0c;不考虑过程调用的分析&#xff…...

零信任实践:OpenClaw+SecGPT-14B构建个人安全决策引擎

零信任实践&#xff1a;OpenClawSecGPT-14B构建个人安全决策引擎 1. 为什么需要个人安全决策引擎 去年某个深夜&#xff0c;我的服务器突然收到大量异常登录尝试。虽然最终没有造成损失&#xff0c;但这件事让我意识到&#xff1a;传统的静态密码和固定权限规则&#xff0c;在…...

基于条件风险价值CVaR的微网/虚拟电厂多场景随机规划 摘要:构建了含风、光、燃、储的微网/虚...

基于条件风险价值CVaR的微网/虚拟电厂多场景随机规划 摘要&#xff1a;构建了含风、光、燃、储的微网/虚拟电厂优化调度模型&#xff0c;在此基础上&#xff0c;考虑多个风光出力场景&#xff0c;构建了微网随机优化调度模型&#xff0c;并在此基础上&#xff0c;基于条件风险价…...

Swagger Client 与微服务架构:如何管理多个 API 端点的终极方案

Swagger Client 与微服务架构&#xff1a;如何管理多个 API 端点的终极方案 【免费下载链接】swagger-js Javascript library to connect to swagger-enabled APIs via browser or nodejs 项目地址: https://gitcode.com/gh_mirrors/sw/swagger-js 在现代微服务架构中&a…...

Intv_ai_mk11 C++高性能集成开发教程

Intv_ai_mk11 C高性能集成开发教程 1. 为什么需要高性能C集成方案 在AI应用开发中&#xff0c;性能往往是关键瓶颈。当你的C应用需要频繁调用AI模型API时&#xff0c;一个高效的集成方案能带来显著差异。想象一下&#xff0c;你正在开发一个实时视频分析系统&#xff0c;每秒…...

OpenClaw云端服务器搭建指南:2026年部署、配置大模型百炼APIKey、集成Skill超详细流程

OpenClaw云端服务器搭建指南&#xff1a;2026年部署、配置大模型百炼APIKey、集成Skill超详细流程。 OpenClaw&#xff08;原Clawdbot&#xff09;作为2026年主流的AI自动化助理平台&#xff0c;可通过阿里云轻量服务器实现724小时稳定运行&#xff0c;并快速接入钉钉&#xff…...

OpenClaw开发助手:Qwen3.5-9B支持的代码调试与日志分析

OpenClaw开发助手&#xff1a;Qwen3.5-9B支持的代码调试与日志分析 1. 为什么开发者需要AI辅助调试&#xff1f; 深夜两点&#xff0c;我盯着终端里不断刷新的错误日志&#xff0c;第17次尝试修复那个诡异的空指针异常。咖啡杯早已见底&#xff0c;而问题依然像迷宫般无解——…...

深入解析 OpenSTLinux 6.6 Yocto SDK 环境配置与 BSP 源码部署 - STM32MP2 实战(基于STM32CubeMX)

1. OpenSTLinux 6.6 Yocto SDK环境配置全攻略 刚拿到STM32MP2开发板时&#xff0c;最让人头疼的就是搭建开发环境。我花了整整三天时间才把Yocto SDK环境配置明白&#xff0c;今天就把这些实战经验分享给大家&#xff0c;让你少走弯路。 首先需要下载两个关键文件&#xff1a;S…...

Geekble测谎模块Arduino库:GSR生理信号采集与多模态反馈

1. 项目概述Geekble_LieDetector 是一款面向嵌入式平台&#xff08;典型为基于ATmega328P的Arduino兼容控制器&#xff09;设计的生理信号检测与交互控制库&#xff0c;专用于驱动 Geekble LieDetector 模块。该模块并非传统意义上的“测谎仪”&#xff0c;而是一个以皮肤电导&…...

ZYNQ AXI_DMA配置避坑指南:如何避免DDR3数据传输中的栈区溢出

ZYNQ AXI_DMA配置避坑指南&#xff1a;如何避免DDR3数据传输中的栈区溢出 在嵌入式系统开发中&#xff0c;内存管理往往是决定项目成败的关键因素之一。最近接手一个ZYNQ项目时&#xff0c;我遇到了一个令人头疼的问题&#xff1a;当使用AXI_DMA从PL端向PS端的DDR3内存传输大量…...

C语言宏定义封装函数参数的工程实践

1. 宏定义封装函数参数的核心价值在嵌入式开发中&#xff0c;我们经常遇到需要传递大量固定参数的场景。以NXP RT1052 SDK中的GPIO配置为例&#xff0c;每个引脚复用配置需要传递6个参数&#xff0c;其中5个都是固定值。这种场景下&#xff0c;宏定义封装技术能显著提升代码的可…...