插件允许对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的所有方法,都会经过Plugin的invoke方法,去看看

    // 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. Java学习笔记-基础语法Ⅸ-文件

    File File是文件和路径名的抽象表示,File封装的并不是一个真正存在的文件,是一个路径名,可以存在也可以不存在 常用方法: 创建文件:createNewFile() 创建目录:mkdir() ...

  2. Redisson报错

    org.redisson.client.RedisResponseTimeoutException: Redis server response timeout (3000 ms) occured a ...

  3. 干掉RedisHelper,请这样用分布式缓存

    前言 我们在项目中使用Redis时通常是写一个单例模式的RedisHelper静态类,暴露一些常用的Get.Set等操作,在需要使用地方直接RedisHelper.StringGet(xx,xx)就可 ...

  4. 739. Daily Temperatures - LeetCode

    Question 739. Daily Temperatures Solution 题目大意:比今天温度还要高还需要几天 思路:笨方法实现,每次遍历未来几天,比今天温度高,就坐标减 Java实现: p ...

  5. python之模块(hashlib、logging)

    hashlib模块 加密的概念 加密,是以某种特殊的算法改变原有的数据,当其他人获得数据时,也无法了解数据的内容.简单的来说,就是将明文(人看得懂)数据通过一些手段变成密文数据(人看不懂),密文数据的 ...

  6. AMS 新闻视频广告的云原生容器化之路

    作者 卓晓光,腾讯广告高级开发工程师,负责新闻视频广告整体后台架构设计,有十余年高性能高可用海量后台服务开发和实践经验.目前正带领团队完成云原生技术栈的全面转型. 吴文祺,腾讯广告开发工程师,负责新闻 ...

  7. 记一次Tomcat卡死在 Deploying web application 步骤的问题

    公司有一个历史的遗留项目是传统的MVC架构的前后不分离的项目,一开始使用JDK1.7写的,后来前一阵老板说想在这个远古项目上加点功能,顺带换换皮,于是乎一帮程序员们就用JDK1.8重新翻新了一遍项目顺 ...

  8. 关于基础RMQ——ST算法

    RMQ,Range Maximum/Minimum Query,顾名思义,就是询问某个区间内的最大值或最小值,今天我主要记录的是其求解方法--ST算法 相对于线段树,它的运行速度会快很多,可以做到O( ...

  9. Java有根儿:Class文件以及类加载器

    JVM 是Java的基石,Java从业者需要了解.然而相比JavaSE来讲,不了解JVM的一般来说也不会影响到工作,但是对于有调优需求或者系统架构师的岗位来说,JVM非常重要.JVM不是一个新的知识, ...

  10. MySQL锁(乐观锁、悲观锁、多粒度锁)

    锁 并发事务可能出现的情况: 读-读事务并发:此时是没有问题的,读操作不会对记录又任何影响. 写-写事务并发:并发事务相继对相同的记录做出改动,因为写-写并发可能会产生脏写的情况,但是没有一个隔离级别 ...