Mybatis插件

文章目录

  • 1. 如何自定义插件
    • 1.1 创建接口Interceptor的实现类
    • 1.2 配置拦截器
    • 1.3 运行程序
  • 2. 插件原理
    • 2.1 解析过程
    • 2.2 创建代理对象
      • 2.2.1 Executor
      • 2.2.2 StatementHandler
      • 2.2. 3ParameterHandler
      • 2.2.4 ResultSetHandler
    • 2.3 执行流程
    • 2.4 多拦截器的执行顺序
  • 3.

1. 如何自定义插件

1.1 创建接口Interceptor的实现类

/**
 * @author Clinton Begin
 */
public interface Interceptor {

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

  // 决定是否触发 intercept()方法
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  // 根据配置 初始化 Intercept 对象
  default void setProperties(Properties properties) {
    // NOP
  }

}

mybatis运行拦截的内容包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

定义一个实现类。


/**
 * MyBatis中的自定义的拦截器
 *
 * @Signature 表示一个方法签名,唯一确定一个方法
 */
@Intercepts(
        {@Signature(
                type = Executor.class, // 拦截类型
                method = "query", // 拦截方法
                // args 中指定 被拦截方法的 参数列表
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(
                        type = Executor.class,
                        method = "close",
                        args = {boolean.class})
        })
public class MyInterceptor implements Interceptor {

    private String interceptorName;

    public String getInterceptorName() {
        return interceptorName;
    }

    /**
     * 执行拦截的方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("------MyInterceptor  before---------");
        Object proceed = invocation.proceed();
        System.out.println("------MyInterceptor  after---------");
        return proceed;
    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("setProperties : " + properties.getProperty("interceptorName"));
        this.interceptorName = properties.getProperty("interceptorName");
    }
}

1.2 配置拦截器

 <plugins>
        <plugin interceptor="com.boge.interceptor.MyInterceptor">
            <property name="interceptorName" value="myInterceptor"/>
        </plugin>
 </plugins>

1.3 运行程序

    @Test
    public void test2() throws Exception{
        // 1.获取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.加载解析配置文件并获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.根据SqlSessionFactory对象获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 4.通过SqlSession中提供的 API方法来操作数据库
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Integer param = 1;
        User user = mapper.selectUserById(param);
        System.out.println(user);
}

拦截的query方法和close方法的源码位置在如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 插件原理

2.1 解析过程

解析全局配置文件过程中,查看XMLConfigBuilder类的方法parseConfiguration。
在这里插入图片描述

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取<plugin> 节点的 interceptor 属性的值
        String interceptor = child.getStringAttribute("interceptor");
        // 获取<plugin> 下的所有的properties子节点
        Properties properties = child.getChildrenAsProperties();
        // 获取 Interceptor 对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        // 设置 interceptor的 属性
        interceptorInstance.setProperties(properties);
        // Configuration中记录 Interceptor
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

该方法主要创建Interceptor 对象,并设置属性,最终放在configuration对象的InterceptorChain里面
在这里插入图片描述
在这里插入图片描述

来看InterceptorChain的源码。

/**
 * InterceptorChain 记录所有的拦截器
 * @author Clinton Begin
 */
public class InterceptorChain {

  // 保存所有的 Interceptor  也就我所有的插件是保存在 Interceptors 这个List集合中的
  private final List<Interceptor> interceptors = new ArrayList<>();

  // 现在我们定义的有一个 Interceptor MyInterceptor
  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);
  }

}

可以看到拦截器放在这个list变量interceptors 。

2.2 创建代理对象

2.1步骤创建了拦截器,并且保存在InterceptorChain,**那拦截器如何与目标对象关联?**拦截器拦截对象包括:Executor,ParameterHandler,ResultSetHandler,StatementHandler. 这些对象创建的时候需要注意什么?

2.2.1 Executor

在创建SqlSession的过程中,会创建执行器Executor。可以看到Executor植入插件

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) { // 针对 Statement 对象做缓存
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 默认 SimpleExecutor 每一次只是SQL操作都创建一个新的Statement对象
      executor = new SimpleExecutor(this, transaction);
    }
    // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
    // 映射文件中 <cache> 标签 --> 创建 Cache对象
    // settings 中的 cacheEnabled = true 真正的对 Executor 做了缓存的增强
    if (cacheEnabled) {
      // 穿衣服的事情 --> 装饰器模式
      executor = new CachingExecutor(executor);
    }
    // 植入插件的逻辑,至此,四大对象已经全部拦截完毕
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

在这里插入图片描述

进入pluginAll方法:

 // 现在我们定义的有一个 Interceptor MyInterceptor
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) { // 获取拦截器链中的所有拦截器
      target = interceptor.plugin(target); // 创建对应的拦截器的代理对象
    }
    return target;
  }

再进入plugin方法

在这里插入图片描述
在这里插入图片描述

再查看Plugin工具类的实现 wrap方法。


  /**
   * 创建目标对象的代理对象
   *    目标对象 Executor  ParameterHandler  ResultSetHandler StatementHandler
   * @param target 目标对象
   * @param interceptor 拦截器
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    // 获取用户自定义 Interceptor中@Signature注解的信息
    // getSignatureMap 负责处理@Signature 注解  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;
  }

getSignatureMap方法
在这里插入图片描述
在这里插入图片描述
再来看Plugin的源码。


/**
 * @author Clinton Begin
 */
public class Plugin implements InvocationHandler {

  private final Object target; // 目标对象
  private final Interceptor interceptor; // 拦截器
  private final Map<Class<?>, Set<Method>> signatureMap; // 记录 @Signature 注解的信息

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  /**
   * 创建目标对象的代理对象
   *    目标对象 Executor  ParameterHandler  ResultSetHandler StatementHandler
   * @param target 目标对象
   * @param interceptor 拦截器
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    // 获取用户自定义 Interceptor中@Signature注解的信息
    // getSignatureMap 负责处理@Signature 注解  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;
  }

  /**
   * 代理对象方法被调用时执行的代码
   * @param proxy
   * @param method
   * @param args
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 获取当前方法所在类或接口中,可被当前Interceptor拦截的方法 Executor query
      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);
    }
  }

  /**
   * 获取拦截器中的 @Intercepts 注解中的相关内容
   * @param interceptor
   * @return
   */
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // 获取 @Intercepts 注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    // 获取 @Signature 注解中的内
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      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<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        // 判断 目标对象的 接口类型是否在 @Signature 注解中声明的有
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      // 继续获取父类
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

}

2.2.2 StatementHandler

在这里插入图片描述
在这里插入图片描述

2.2. 3ParameterHandler

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.2.4 ResultSetHandler

在这里插入图片描述
在这里插入图片描述

2.3 执行流程

以Executor的query方法为例,实际执行的是代理对象。

在这里插入图片描述

然后会执行Plugin的invoke方法。
在这里插入图片描述
然后进入interceptor.intercept,进入自定义拦截器
在这里插入图片描述

2.4 多拦截器的执行顺序

在这里插入图片描述

总结:

对象作用
Interceptor自定义插件需要实现接口,实现4个方法
InterceptChain配置的插件解析后会保存在Configuration的InterceptChain中
Plugin触发管理类,还可以用来创建代理对象
Invocation对被代理类进行包装,可以调用proceed()调用到被拦截的方法

3.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】内存数据库Redis进阶(Redis持久化)

目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…

Git全栈体系(三)

第六章 GitHub 操作 一、创建远程仓库 二、远程仓库操作 命令名称作用git remote -v查看当前所有远程地址别名git remote add 别名 远程地址起别名git push 别名 分支推送本地分支上的内容到远程仓库git clone 远程地址将远程仓库的内容克隆到本地git pull 远程库地址别名 远…

基于SpringCloud+Vue的分布式架构网上商城系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Spring入门-技术简介、IOC技术、Bean、DI

前言 Spring是一个开源的项目&#xff0c;并不是单单的一个技术&#xff0c;发展至今已形成一种开发生态圈。也就是说我们可以完全使用Spring技术完成整个项目的构建、设计与开发。Spring是一个基于IOC和AOP的架构多层j2ee系统的架构。 SpringFramework&#xff1a;Spring框架…

06-向量的更多术语和表示法

向量 引入的概念&#xff1a;向量就是一组有序的数字, 我们在理解它的时候&#xff0c; 可以把它理解成是一个有效的线段&#xff0c;也可以把它理解成是空间中的一个点&#xff0c;那么与之相对应的一个数字&#xff0c;也就是我们在初等数学中学的一个一个数&#xff0c;我们…

GRNN神经网络原理与matlab实现

1案例背景 1.1GRNN神经网络概述 广义回归神经网络(GRNN Generalized Regression Neural Network&#xff09;是美国学者 Don-ald F. Specht在1991年提出的,它是径向基神经网络的一种。GRNN具有很强的非线性映射能力和柔性网络结构以及高度的容错性和鲁棒性,适用于解决非线性问…

关于综合能源智慧管理系统的架构及模式规划的研究

安科瑞 华楠 摘 要&#xff1a;探讨了国内外能源互联网的研究发展&#xff0c;分析了有关综合智慧能源管理系统的定位&#xff0c;以及系统的主要特点&#xff0c;研究了综合智慧能源管理系统的构架以及模式规划。 关键词&#xff1a;综合能源&#xff1b;智慧管理系统&#…

如何在不使用脚本和插件的情况下手动删除 3Ds Max 中的病毒?

如何加快3D项目的渲染速度&#xff1f; 3D项目渲染慢、渲染卡顿、渲染崩溃&#xff0c;本地硬件配置不够&#xff0c;想要加速渲染&#xff0c;在不增加额外的硬件成本投入的情况下&#xff0c;最好的解决方式是使用渲云云渲染&#xff0c;在云端批量渲染&#xff0c;批量出结…

【PHP代码审计】ctfshow web入门 php特性 93-104

ctfshow web入门 php特性 93-104 web 93web 94web 95web 96web 97web 98web 99web 100web 101web 102web 103web 104 web 93 这段PHP代码是一个简单的源码审计例子&#xff0c;让我们逐步分析它&#xff1a; include("flag.php");: 这行代码将flag.php文件包含进来。…

从零开始学python(十二)如何成为一名优秀的爬虫工程师

前言 回顾之前讲述了python语法编程 必修入门基础和网络编程&#xff0c;多线程/多进程/协程等方面的内容&#xff0c;后续讲到了数据库编程篇MySQL&#xff0c;Redis&#xff0c;MongoDB篇&#xff0c;和机器学习&#xff0c;全栈开发&#xff0c;数据分析前面没看的也不用往…

SSL原理详解

SSL协议结构&#xff1a; SSL协议分为两层&#xff0c;下层为SSL记录协议&#xff0c;上层为SSL握手协议、SSL密码变化协议和SSL警告协议。 1.下层为SSL记录协议&#xff0c;主要作用是为高层协议提供基本的安全服务 建立在可靠的传输之上&#xff0c;负责对上层的数据进行分块…

DeepVO 论文阅读

论文信息 题目&#xff1a;DeepVO Towards End-to-End Visual Odometry with Deep Recurrent Convolutional Neural Networks 作者&#xff1a;Sen Wang, Ronald Clark, Hongkai Wen and Niki Trigoni 代码地址&#xff1a;http://senwang.gitlab.io/DeepVO/ (原作者并没有开源…

【C++】从0到1讲继承|复杂的菱形继承

个人主页&#xff1a;&#x1f35d;在肯德基吃麻辣烫 我的gitee&#xff1a;gitee仓库 分享一句喜欢的话&#xff1a;热烈的火焰&#xff0c;冰封在最沉默的火山深处。 前言 本文主要讲述的是继承的概念&#xff0c;以及基类和派生类和衍生出的各种东西&#xff0c;还有多继承…

前端代码注释率

nodejs差代码注释率 /*** author duan* source https://editor.csdn.net/md/?not_checkout1&spm1011.2124.3001.6192* date 2023-7-7* * 统计指定目录下代码行数及注释率* * 用法: node count.js <路径> [后缀名]...* 后缀名不填的话默认为统计 .js 和 .ts 文件* *…

Jenkins通过OpenSSH发布WinServer2016

上一篇文章> Jenkins集成SonarQube代码质量检测 一、实验环境 jenkins环境 jenkins入门与安装 容器为docker 主机IP系统版本jenkins10.10.10.10rhel7.5 二、OpenSSH安装 1、下载 官网地址&#xff1a;https://learn.microsoft.com/zh-cn/windows-server/administration/op…

MaxPatrol SIEM 增加了一套检测供应链攻击的专业技术

我们为 MaxPatrol SIEM 信息安全事件监控系统增加了一套新的专业技术。 该产品可帮助企业防范与供应链攻击相关的威胁。 此类攻击正成为攻击者的首要目标&#xff1a;它们以软件开发商和供应商为目标&#xff0c;网络犯罪分子通过他们的产品进入最终目标的基础设施。 因此&a…

Android Studio 启用设备远程调试配置完整步聚

启用手机设置->开发者选项-无线调试,然后选择允许 已启用后无线调试变成绿色 ,点击无线调试进入详情页面 点击Android Studio的Device Manager 下的WIFI图标 会弹出下图窗口 打开手机的开发者选项中的WIFI调试(无线调试)下的使用二维码配对设备进行扫描. 设备配对成功后手机…

带wiringPi库的交叉编译 ---宿主机x86Ubuntu,目标机ARMv8 aarch64(香橙派)

带wiringPi库的交叉编译如何进行 先交叉编译wiringPi库&#xff0c;编译出的库适合香橙派&#xff0c;这时候交叉编译可执行程序的平台和链接库的格式也是正确的&#xff0c;然后通过-I和-L来指定链接的wiringPi库的头文件和库的位置&#xff0c;但是现在还没有学习过&#xf…

Reinforcement Learning with Code 【Code 1. Tabular Q-learning】

Reinforcement Learning with Code 【Code 1. Tabular Q-learning】 This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation o…

【Redis】内存数据库 Redis 基础

目录 内存数据库Redis概念Redis 安装Redis的启动方式Redis命令行客户端 Redis通用命令Redis key结构Redis value数据类型String 和基础操作Hash 和基础操作List 和基础操作Set 和基础操作Sorted_set 和基础操作 Redis的Java客户端Jedis客户端SpringDataRedis客户端自定义RedisT…
最新文章