在使用mybatis的时候,我们可以自己指定plugin,在sql执行过程中,增加一些额外的逻辑处理,这篇笔记主要记录plugin的原理

使用

1、声明自定义的plugin

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class , RowBounds.class, ResultHandler.class}))
public class TestPlugin1 implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("测试插件1被执行");return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}

在自定义的plugin插件中,我们需要通过注解,执行当前插件作用于哪个对象?
在mybatis底层,有四个比较重要的对象,分别是:executor、parameterHandler、ResultSetHandler、StatementHandler

2、在全局配置文件中,增加plugin节点的配置

<plugins><plugin interceptor="org.apache.ibatis.study.nativestu.TestPlugin1"></plugin><plugin interceptor="org.apache.ibatis.study.nativestu.TestPlugin2"></plugin>
</plugins>

这样即可,在sql被执行的时候,会被自定义的plugin拦截到,执行对应的intercept()方法
但是,在使用的时候,需要通过注解,指定当前插件对哪个类生效,在mybatis底层,有四个比较重要的类,分别是:executor、parameterHandler、ResultSetHandler、StatementHandler

需要声明当前插件是作用于哪个类的哪个方法,我上面指定的是executor的query方法,为什么是query方法,因为不管是selectList还是selectOne,底层都是调用的executor的query方法

源码

在mybatis中的plugin,其实就是拦截器,自然,也肯定是通过代理来实现的

解析plugin

我们先说解析的逻辑,在mybatis中,会解析全局配置文件,在解析全局配置文件的时候,会解析配置,将我们自定义的plugin,转换为interceptor对象存储
在前面博客中,mybatis源码:mybatis的sql解析 有提到过,原生的mybatis,是从SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 这里的build方法开始的,所有的解析逻辑,都会在这个方法中完成;
所以我们直接调到这个方法中

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

这个方法是mybatis在解析了节点之后,会依次解析其子节点的方法
在这个方法中,有一行和本篇博客有关的代码

pluginElement(root.evalNode(“plugins”));

private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}
}public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);
}org.apache.ibatis.plugin.InterceptorChain
public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}
private final List<Interceptor> interceptors = new ArrayList<>();

上面这个方法,入参的node节点,就是,可以看到,在方法中,会获取到期children节点,然后依次获取到对应的interceptor配置,也就是我们自定义的插件的全类目,然后根据全类名,通过newInstance()方法来初始化对象,然后把对象设置到了configuration的interceptor属性中,最终,会添加到一个list集合中

以上,就是mybatis在启动时,解析plugin的逻辑

使用

把所有的自定义插件,解析成interceptor,然后存入到list集合中,自然是为了使用

SqlSession sqlSession = sqlSessionFactory.openSession();

这是要讲的代码,在这里的openSession()方法中,会获取到一个sqlSession对象,在初始化对象的时候,会先初始化一个executor对象,

这里中间的代码,不是本篇博客要关心的,所以就跳过,调用链可以参考上面的视频,我们只关心初始化executor的这个方法

org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {// 默认的executor是SimpleExecutorexecutorType = 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,这里的executor会被包装在cachingExecutor中if (cacheEnabled) {executor = new CachingExecutor(executor);}/*** 上面的逻辑是根据不同的executorType类型来初始化不同的执行器* 下面是为executor生成动态代理对象*/executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

这一段代码,就是根据当前指定的executorType初始化不同的对象,我们需要关心的,是倒数第二行代码

org.apache.ibatis.plugin.InterceptorChain
public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;
}

可以看到,这个方法就是从上面解析之后的list集合中,依次遍历,调用自定义插件的plugin()方法;
我们在自定义的plugin对应的plugin()方法中,调用的是Plugin.wrap()

所以,我们接着来看Plugin.wrap()的源码

public static Object wrap(Object target, Interceptor interceptor) {//1.获取@Intercepts注解的配置信息Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);// 2.获取当前target的class(这里的target,是从前面入参进来的,就是mybatis的四大对象其中的一个)Class<?> type = target.getClass();// 3.判断注解配置的类中,是否包含当前入参的targetClass<?>[] interfaces = getAllInterfaces(type, signatureMap);/*** 4.如果包含,就生成代理对象* 可以看到这里入参的invocation对象,就是plugin*/if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}

所以,这几段代码的逻辑,连起来看,是这样的:

  1. 在初始化executor对象之后,会判断是否需要给当前executor生成动态代理对象
  2. 判断的依据是什么?就是我们自定义的插件,是否有要作用于executor对象的
  3. 如果有,就依次通过jdk代理,生成代理对象
  4. 所以,如果我们自定义了插件,作用于executor对象,最后生成的executor对象是一个代理对象,而这个代理对象,是包了N层(这里的N层就是指有N对plugin要作用于executor对象)

在我们执行selectList查询的时候,依赖于executor对象的query方法,所以,会被代理对象所拦截到,就会执行我们自定义插件的interceptor()方法

结论

所以,对于自定义插件的原理,大致是这样的:

  1. 在解析全局配置文件的时候,会解析; 然后将自定义的插件,转换为interceptor对象,存到内存中,以list集合的形式存入
  2. 然后在初始化executor时,会对executor进行增强处理
  3. 根据自定义的插件相关配置,来判断是否要对executor进行增强,如果需要增强,会通过jdk动态代理生成代理对象
  4. 在执行sql的时候,会被代理对象拦截到,然后先执行对应的intercept方法

mybatis源码-plugin源码相关推荐

  1. Android Gradle Plugin 源码阅读与编译

    前言 为了解一些Andorid的构建流程,有时候需要阅读Android Gradle Plugin的相关源码的.自己阅读Android Gradle Plugin源码主要经历了三个时期: 1.AOSP ...

  2. Kubernetes学习笔记之Calico CNI Plugin源码解析(二)

    女主宣言 今天小编继续为大家分享Kubernetes Calico CNI Plugin学习笔记,希望能对大家有所帮助. PS:丰富的一线技术.多元化的表现形式,尽在"360云计算" ...

  3. 【Mybatis源码】源码分析

    [Mybatis源码]源码分析 (一)Mybatis重要组件 [1]四大核心组件 (1)SqlSessionFactoryBuilder (2)SqlSessionFactory (3)SqlSess ...

  4. 最新码支付源码+完整版+免挂监听回调+微信、支付宝、qq监控APP打包教程

    码支付手机APP打包教程 码支付官网:https://pay.madanbao.com 1.打开uniapp的官网注册一个账号,网址为:www.dcloud.io/ 3.打开工具导入项目,打开mani ...

  5. Spring5源码系列-02-源码整体脉络

    简介 Spring框架源码拥有约108万行代码,如果要把所有的代码都看一遍,是需要花费大量时间和精力,而且很容易跟进一个方法绕进去,所以我们需要抓住Spring源码主干源码和Spring源码对各种设计 ...

  6. java 头尾 队列_源码|jdk源码之栈、队列及ArrayDeque分析

    栈.队列.双端队列都是非常经典的数据结构.和链表.数组不同,这三种数据结构的抽象层次更高.它只描述了数据结构有哪些行为,而并不关心数据结构内部用何种思路.方式去组织. 本篇博文重点关注这三种数据结构在 ...

  7. Flink源码分析 - 源码构建

    本篇文章首发于头条号Flink源码分析 - 源码构建,欢迎关注我的头条号和微信公众号"大数据技术和人工智能"(微信搜索bigdata_ai_tech)获取更多干货,也欢迎关注我的C ...

  8. Dapper源码学习和源码修改(下篇)

    继上篇Dapper源码学习和源码修改 讲了下自己学习Dapper的心得之后,下篇也随之而来,上篇主要讲的入参解析那下篇自然主打出参映射了. 好了,废话不多说,开始吧. 学习之前你的先学习怎么使用Dap ...

  9. [Android源码]Android源码之高仿飞鸽传书WIFI热点搜索与创建(一)

    (本文详情来源:android源码 http://www.eoeandroid.com/thread-296427-1-1.html   转载请注明出处!)  [Android源码分享]飞鸽传书的An ...

最新文章

  1. 多目标跟踪(MOT)入门
  2. nacos如何做注册中心?服务注册之后自带负载均衡,这一篇文章就够了!
  3. fortran subroutine_Fortran:派生数组与数组传递进子程序耗费时间比较
  4. java中or和and的优先级_x86处理器汇编语言AND和OR运算符优先级
  5. Ocelot中文文档入门
  6. android闹钟延时,android闹钟定时启动延时或者直接不启动
  7. 网页优化系列三:使用压缩后置viewstate
  8. 强大的火狐插件(转)
  9. 基于C语言的移位密码和仿射密码
  10. mongodb3 重启_冰点还原的安装配置,每次重启就会还原系统软件。
  11. 机器学习梯度消失,梯度爆炸原因
  12. 考试系统设计oracle,在线考试系统的设计与实现|毕业设计源代码|论文开题报告|Oracle...
  13. Oracle触发器(当A表新增/修改/删除时,同步数据到B表)
  14. L2范数-欧几里得范数
  15. 虚拟机NAT模式无法连外网
  16. 沪江网校前端架构漫谈
  17. Ubuntu 22.10 (Kinetic Kudu) 发布
  18. 我用了九个小时给高中同学写了一款留言板
  19. Javaweb Session与Cookie(自定义Session)
  20. Harrison Consoles 软件促销

热门文章

  1. 【电商网站】将商品加入购物车代码
  2. 我亲爱的朋友们_亲爱的lazyweb-我该如何处理所有电子邮件?
  3. 关于serverlet
  4. Android之viewPager嵌套viewPager无法滑动子viewPager
  5. 用计算机画函数图象,信息技术应用 用计算机画函数图象 .doc
  6. “51媒体网“媒体邀约,媒体现场采访的优势
  7. abb机器人控制箱按键的作用_ABB机器人控制柜各个部件及作用介绍
  8. 如何查看国内sci期刊有哪些
  9. Python解释器的选择,初学者必看
  10. 缓冲区溢出攻击(详细解析)