插件允许对Mybatis的四大对象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)进行拦截

问题

Mybatis插件的注册顺序与调用顺序的关系?

使用

在讲源码之前,先看看如何自定义插件。

mybatis-demo官方文档

  1. 创建插件类

    自定义插件类需要实现Interceptor

    // 注解配置需要拦截的类以及方法
    @Intercepts({
    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
    })
    // 实现Interceptor接口
    public class SqlLogPlugin implements Interceptor { /**
    * 具体的拦截逻辑
    */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    long begin = System.currentTimeMillis();
    try {
    return invocation.proceed();
    } finally {
    long time = System.currentTimeMillis() - begin;
    System.out.println("sql 运行了 :" + time + " ms");
    }
    } /**
    * 判断是否需要进行代理
    * 此方法有默认实现,一般无需重写
    */
    /*@Override
    public Object plugin(Object target) {
    return Plugin.wrap(target, this);
    }*/ /**
    * 自定义参数
    */
    @Override
    public void setProperties(Properties properties) {
    // 这是xml中配置的参数
    properties.forEach((k, v) -> {
    System.out.printf("SqlLogPlugin---key:%s, value:%s%n", k, v);
    });
    }
    }
  2. 注册

    在配置文件注册插件

    <plugins>
    <plugin interceptor="com.wjw.project.intercaptor.SqlLogPlugin">
    <property name="key1" value="root"/>
    <property name="key2" value="123456"/>
    </plugin>
    </plugins>
  3. 效果

    控制输出

    SqlLogPlugin---key:key1, value:root
    SqlLogPlugin---key:key2, value:123456
    sql 运行了 :17 ms

源码

原理:Mybatis四大对象创建时,都回去判断是否满足插件的拦截条件,满足,则四大对象就会被Plugin类代理

源码分3部分讲。注册、包装、调用

  1. 注册

    xml方式的注册,是在XMLConfigBuilder#pluginElement完成的。

    不明觉厉的同学,请参考上一篇文章:Mybatis源码解读-配置加载和Mapper的生成

    // XMLConfigBuilder#pluginElement(XNode parent)
    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).getDeclaredConstructor().newInstance();
    interceptorInstance.setProperties(properties);
    // 将插件添加到配置的插件链中,等待后续使用
    configuration.addInterceptor(interceptorInstance);
    }
    }
    }

    configuration.addInterceptor做得操作很简单

  2. 包装

    上面讲了插件的注册,最后调用的是configuration.addInterceptor,最终调用的是InterceptorChain#addInterceptor

    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<>();
    /*
    * 每当四大对象创建时,都会执行此方法
    * 满足拦截条件,则返回Plugin代理,否则返回原对象
    * @param target Mybatis四大对象之一
    */
    public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
    // 调用每个插件的plugin方法,判断是否需要代理
    target = interceptor.plugin(target);
    }
    return target;
    }
    // 将拦截器添加interceptors集合中存起来
    public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
    } public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
    } }

    我们案例是拦截StatementHandler,所以也以此为例

    /*
    * 这是创建StatementHandler的方法
    * Configuration#newStatementHandler
    */
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 可以看到创建完StatementHandler之后,会调用InterceptorChain的pluginAll方法
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
    }

    那么我们再仔细分析下pluginAll方法,pluginAll调用的是每个插件的plugin方法

    default Object plugin(Object target) {
    return Plugin.wrap(target, this);
    }

    可以看到,最终调用的是Plugin.*wrap*

    /*
    * Plugin#wrap
    * 判断是否满足插件的拦截条件,是则返回代理类,否则返回原对象
    */
    public static Object wrap(Object target, Interceptor interceptor) {
    // 获取插件的拦截信息(就是获取@Intercepts注解的内容)
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 判断是否满足拦截条件
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
    // 满足拦截条件则返回Plugin代理对象
    return Proxy.newProxyInstance(
    type.getClassLoader(),
    interfaces,
    new Plugin(target, interceptor, signatureMap));
    }
    // 不满足则返回原对象
    return target;
    }
  3. 调用

    在上一个包装步骤提到,满足条件会返回代理对象,即调用StatementHandler的所有方法,都会经过Plugininvoke方法,去看看

    // Plugin#invoke
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    // 获取拦截条件(需要拦截的方法)
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    if (methods != null && methods.contains(method)) {
    // 满足拦截条件,则调用插件的intercept方法
    return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
    } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
    }
    }

Mybatis源码解读-插件的更多相关文章

  1. MyBatis源码解读(3)——MapperMethod

    在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在 ...

  2. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  3. MyBatis源码解读之延迟加载

    1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当 ...

  4. MyBatis 源码篇-插件模块

    本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...

  5. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  6. Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

    本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...

  7. MyBatis源码解读(1)——SqlSessionFactory

    在前面对MyBatis稍微有点了解过后,现在来对MyBatis的源码试着解读一下,并不是解析,暂时定为解读.所有对MyBatis解读均是基于MyBatis-3.4.1,官网中文文档:http://ww ...

  8. 【转】Mybatis源码解读-设计模式总结

    原文:http://www.crazyant.net/2022.html?jqbmtw=b90da1&gsjulo=kpzaa1 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开 ...

  9. Mybatis源码解读-设计模式总结

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

随机推荐

  1. linux系统平均负载高(load average)

    系统平均负载高(load average) 问题现象 两个案例都是:系统平均负载高,但cpu,内存,磁盘io都正常 什么是系统平均负载 平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进 ...

  2. linux下运行crm

    linux下运行crm 1.从windows把crm代码拷贝到linux服务器上 2.学习virtualenvwrapper工具升级版 1.安装 pip3 install virtualenvwrap ...

  3. 用 Python 远程控制 Windows 服务器,太好用了!

    在很多企业会使用闲置的 Windows 机器作为临时服务器,有时候我们想远程调用里面的程序或查看日志文件 Windows 内置的服务「 winrm 」可以满足我们的需求 它是一种基于标准简单对象访问协 ...

  4. VMware虚拟机中安装Linux操作系统(ubuntu)

    一.准备工作: 1.下载VMware虚拟机 下载地址:https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evalua ...

  5. MyBatisPlus详解

    1.MyBatisPlus概述 需要的基础:MyBatis.Spring.SpringMVC 为什么要学习?MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成! 简介 ...

  6. Spark框架——WordCount案例实现

    package wordcount import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} ...

  7. 可靠的分布式KV存储产品-ETCD-初见

    目录 Paxos Raft(Understandable Distributed Consensus) 名词介绍 Leader Election Log Replication 请求完整流程 etcd ...

  8. 《HALCON数字图像处理》第五章笔记

    目录 第五章 图像运算 图像的代数运算 加法运算 图像减法 图像乘法 图像除法 图像逻辑运算(位操作) 图像的几何变换 图像几何变换的一般表达式 仿射变换 投影变换 灰度插值 图像校正 我在Gitee ...

  9. python基础学习7

    python基础学习7 内容概要 字符串的内置方法 字符串的内置方法(补充) 列表的内置方法 可变类型与不可变类型 队列与堆栈 内容详情 字符串的内置方法 # 1.strip 移除字符串首尾的指定字符 ...

  10. 工作流引擎之Elsa入门系列教程之一 初始化项目并创建第一个工作流

    引子 工作流(Workflow)是对工作流程及其各操作步骤之间业务规则的抽象.概括描述. 为了实现某个业务目标,需要多方参与.按预定规则提交数据时,就可以用到工作流. 通过流程引擎,我们按照流程图,编 ...