Mybatis拦截器源码详解

news2024/11/24 22:30:06

Mybatis拦截器源码详解

  • Mybatis相关全览
  • 一、简介
    • 执行与添加顺序
    • 拦截器生效入口
  • 二、使用
    • 例子
  • 三、原理
    • 加载入口
    • 生成代理
      • 遍历拦截器
      • 匹配&生成代理
  • 四、实践例子

本文用的是3.5.10版本
源码地址:https://github.com/mybatis/mybatis-3/releases
文档地址:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

Mybatis相关全览

一. Mybatis源码详解
二. Mybatis二级缓存详解
三. Mybatis三大执行器介绍
四. Mybatis拦截器源码详解

一、简介

拦截器我相信大家已经非常熟悉了,Mybatis也有拦截器?Mybatis提供了一种插件(plugin)的功能,实际上就是拦截器,像我们平时使用的分页插件也是基于此来实现的

在Mybatis中拦截器可以针对4个点拦截处理,也可以说是4个接口:

  • Executor:拦截执行器的相关方法,可以对这些方法增强处理
  • ParameterHandler:拦截参数的处理,可以对传参做统一处理
  • ResultSetHandler:拦截结果集的处理,可以对返回的结果做统一处理
  • StatementHandler:拦截Sql构建的处理

执行与添加顺序

拦截器主要是拦截以上4个接口,对里面的方法进行增强处理,所以如果以方法来看拦截器的执行顺序是与这几个接口里面的方法在Mybatis执行流程里面的顺序有关,但如果以接口来看拦截器的执行顺序,大概流程是这样(不了解的可以去看看 Mybatis流程源码):

ExecutorParameterHandlerStatementHandlerResultSetHandler

拦截器生效入口

拦截器是什么时候介入到执行流程中来的呢?

以上述4个接口为例,所以有4个介入入口,均是在执行流程中初始化的时候介入的,如下图:

Configuration内部:

在这里插入图片描述

二、使用

使用拦截器其实很简单,我们只需要打上两个注解,实现一个接口,并在配置文件中配置一下就可以了

要打上的两个注解:

  • @Intercepts: 该注解等于是个标识,标识该类是个拦截器,需要配合@Signature来使用
  • @Signature: 该注解也是个标识,表示要拦截的接口以及接口内的哪个方法,有三个参数
    • type :代表我们要拦截的是哪个接口(4个里面选一个)
    • method :代表我们要拦截的是接口里面的哪一个方法(从接口里面去选一个)
    • args :要拦截的方法里面的参数类型,方法里面有几个传参这里就要写几个(因为方法存在重载,名称还无法确定唯一性)

要实现的接口:

Interceptor,内部有三个方法,一个必须要实现,两个随意

  • intercept():必须要实现的方法,这里就可以处理我们的逻辑
  • plugin():可选择实现,返回目标对象或者代理对象
  • setProperties():获取参数,可以从外部获取一些配置参数

下面准备了一个小例子,大家先感受一下

例子

/**
 * 这里我们拦截Executor里面的query和update方法
 * 一个@Signature 代表要拦截的一个方法
 */
@Intercepts({
        /**
         * type     :代表我们要拦截的是哪个接口(4个里面选一个)
         * method   :代表我们要拦截的是接口里面的哪一个方法(从接口里面去选一个)
         * args     :要拦截的方法里面的参数类型,方法里面有几个传参这里就要写几个(因为方法存在重载,名称还无法确定唯一性)
         **/
        @Signature(
              type = Executor.class,
              method = "query",
              args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        ),
        @Signature(
              type = Executor.class,
              method = "update",
              args = {MappedStatement.class, Object.class}
        )
})
public class DemoInterceptor1 implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
      //被代理对象
      Object target = invocation.getTarget();
      //代理方法
      Method method = invocation.getMethod();
      //方法参数
      Object[] args = invocation.getArgs();


      // do something ...方法拦截前执行代码块
      System.out.println("方法拦截前执行 do something ...");

      // 本方法执行(这就是执行被拦截的源方法)
      Object result = invocation.proceed();

      // do something ...方法拦截后执行代码块
      System.out.println("方法拦截后执行 do something ...");
      return result;
    }

    /**
     * 通过该方法决定要返回的对象是目标对象还是对应的代理
     * 一般就两种情况(乱来小心报错):
     * 1. return target;  直接返回目标对象,相当于当前Interceptor没起作用,不会调用上面的intercept()方法
     * 2. return Plugin.wrap(target, this);  返回代理对象,会调用上面的intercept()方法
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 用于获取在Configuration初始化当前的Interceptor时候设置的一些参数
     *
     * @param properties Properties参数
     */
    @Override
    public void setProperties(Properties properties) {
    }
}

配置文件中配置:

  <plugins>
    <plugin interceptor="org.apache.ibatis.interceptor.DemoInterceptor"/>
  </plugins>

由于现在一般都结合springboot开发,这种配置文件的方式可能都被舍弃了,所以在springboot中我们只需要采用@Configuration+@Bean的方式把我们拦截器注入就可以了

三、原理

拦截器类似AOP都属于切面编程思想,底层原理都是动态代理

加载入口

我们以XML配置为例,在解析XML配置的时候就会解析插件(plugins)节点:

在这里插入图片描述

内部就会加载这些拦截器到configuration当中:

在这里插入图片描述

生成代理

还记得上面拦截器的生效入口吗?都执行了一次pluginAll方法吧

  1. 该方法内部就会遍历所有拦截器,执行拦截器里面的plugin方法
  2. 然后通过拦截器打上的那两个注解去匹配
  3. 有匹配的我就给你生成一个代理对象返回,没有就返回原本的对象

在这里插入图片描述

遍历拦截器

在这里插入图片描述

该方法就是我们实现接口后可选择实现的方法之一,有个默认实现的逻辑

在这里插入图片描述

匹配&生成代理

Plugin.wrap方法 就会就获取拦截器上的注解,然后与拦截器的目标对象去匹配,如果匹配上了说明在拦截的范围所以会生成一个代理对象,Plugin同时又实现了InvocationHandler接口,说明代理对象最后执行的时候,会执行Plugin.invoke方法,内部就会判断执行的方法是否是我需要拦截的方法,是则会执行拦截器的拦截方法,不是则执行原方法

在这里插入图片描述

原理咧就是这样,动态代理,对动态代理不了解的,可以先去了解一下

四、实践例子

这里根据上面demo搞了个例子,用处就是打印执行日志,执行的具体方法、执行的完整的SQL语句、执行时间

/**
 * 这里我们拦截Executor里面的query和update方法
 * 一个@Signature 代表要拦截的一个方法
 */
@Intercepts({
        /**
         * type     :代表我们要拦截的是哪个接口(4个里面选一个)
         * method   :代表我们要拦截的是接口里面的哪一个方法(从接口里面去选一个)
         * args     :要拦截的方法里面的参数类型,方法里面有几个传参这里就要写几个(因为方法存在重载,名称还无法确定唯一性)
         **/
        @Signature(
              type = Executor.class,
              method = "query",
              args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        ),
        @Signature(
              type = Executor.class,
              method = "update",
              args = {MappedStatement.class, Object.class}
        )
})
public class DemoInterceptor1 implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
      MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
      Object parameter = null;
      if (invocation.getArgs().length > 1) {
        parameter = invocation.getArgs()[1];
      }
      String sqlId = mappedStatement.getId();
      BoundSql boundSql = mappedStatement.getBoundSql(parameter);
      Configuration configuration = mappedStatement.getConfiguration();
      long sqlStartTime = System.currentTimeMillis();
      Object re = invocation.proceed();
      long sqlEndTime = System.currentTimeMillis();
      // 获取SQL执行语句
      String s = sqlHandler(boundSql, configuration);
      // 打印mysql执行 日志  这里为了方便,不要学我
      System.out.println("-----------------------------------------------------------------");
      System.out.println("SQL的Mapper方法: "+sqlId);
      System.out.println("SQL: "+s);
      System.out.println("SQL执行时间:" + (sqlEndTime - sqlStartTime) + " ms");
      System.out.println("-----------------------------------------------------------------");
      return re;
    }

    /**
     * @Author czl
     * @Description   sql语句里面的?替换成真实的参数
     **/
    private String sqlHandler(BoundSql boundSql,Configuration configuration){
      // 获取mapper里面方法上的参数
      Object sqlParameter = boundSql.getParameterObject();
      TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      // sql原始语句(?还没有替换成我们具体的参数)
      String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      if (parameterMappings != null) {
        for (int i = 0; i < parameterMappings.size(); i++) {
          ParameterMapping parameterMapping = parameterMappings.get(i);
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (sqlParameter == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(sqlParameter.getClass())) {
              value = sqlParameter;
            } else {
              MetaObject metaObject = configuration.newMetaObject(sqlParameter);
              value = metaObject.getValue(propertyName);
            }
            // 上面都是搬的源码里面的  这里的value应该还需要处理,我这里就无脑转String了
            sql = sql.replaceFirst("\\?", value.toString());
          }
        }
      }
      return sql;
    }

    /**
     * 通过该方法决定要返回的对象是目标对象还是对应的代理
     * 一般就两种情况(乱来小心报错):
     * 1. return target;  直接返回目标对象,相当于当前Interceptor没起作用,不会调用上面的intercept()方法
     * 2. return Plugin.wrap(target, this);  返回代理对象,会调用上面的intercept()方法
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 用于获取在Configuration初始化当前的Interceptor时候设置的一些参数
     *
     * @param properties Properties参数
     */
    @Override
    public void setProperties(Properties properties) {
    }

结果如下:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【云原生监控系列第三篇】Prometheus普罗米修斯进阶——PromQL的指标类型

目录一、PromQL 的指标类型1.1 Counter1.2 Gauge1.3 Histogram1.4 Summary1.5 Histogram 与 Summary 的异同二、Prometheus 的聚合函数三、PromQL 的聚合表达式一、PromQL 的指标类型 PromQL 有四个指标类型&#xff1a; Counter &#xff1a;计数器&#xff0c;用于保存单调递…

医疗产品设计的重要性,你了解多少?

医疗产品设计直接关系患者生活的方式与治疗&#xff0c;一个好的医疗产品设计不但要逐渐细化、便于实际操作&#xff0c;而且还要有利于医师操纵&#xff0c;让患者觉得舒服。这是一种具备重大意义的产品。让我们一起来看看有关医疗产品设计的具体内容! 什么叫医疗产品设计? 医…

【Java技术专题】「原理分析系列」Lambda表达式实现原理分析

Lamdba表达式起源 java8引入了lambda表达式是我们java编程方式变革的一个伟大的创举&#xff0c;由了它我们可以采用闭包的形式区开发任何想开发的方法&#xff0c;让java程序与C或者C更加有了贴合的感觉&#xff0c;虽然编程方式和我们目前的命令式编程方式有很大的不同&#…

仿真必修课:计算电磁学入门(附件参考文献与笔记)

转载自电磁CAEer &#xff0c;作者&#xff1a;刘兵 “作为一个电磁设计师&#xff0c;有必要了解计算电磁学吗&#xff1f;” 答案是肯定的。电磁计算从业人员按照分工大致可以分为两类&#xff1a;一类从事CEM&#xff08;计算电磁学&#xff09;&#xff0c;一类从事CAE&a…

ROS 开源项目 TurtleBot3 安装与使用

功能介绍 启动slam完成地图的搭建与保存启动navigation并读取保存的地图&#xff0c;完成自动导航。 注&#xff1a;人工咨询 如果按照下面方案也无法成功解决&#xff0c;可以进入我淘宝咨询&#xff0c;可进行远程辅助解决。 1、安装部分 1.1 创建工作空间lee_ws mkdir…

java项目-第140期ssm高校二手交易平台-ssm毕业设计_计算机毕业设计

java项目-第140期ssm高校二手交易平台-ssm毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm高校二手交易平台》 该项目分为2个角色&#xff0c;管理员和用户。 用户在前台浏览商品&#xff0c;并且可以进行购买。用户购买后可以在后台查看自己的订单等…

论文阅读-基于低秩分解的网络异常检测综述

论文地址&#xff1a;基于低秩分解的网络异常检测综述 摘要&#xff1a; 异常检测对于网络管理与安全至关重要&#xff0e;国内外大量研究提出了一系列网络异常检测方法,其 中大多数方法更关注数据包及其独立时序数据流的分析、检测与告警,这类方法仅仅利用了网络数据之 间的…

嵌入式分享合集104

一、不用串口&#xff0c;如何打印STM32单片机log 本文主要介绍在嵌入式开发中用来输出log的方法。 最常用的是通过串口输出uart log&#xff0c;这种方法实现简单&#xff0c;大部分嵌入式芯片都有串口功能。但是这样简单的功能有时候却不是那么好用&#xff0c;比如&#xf…

入门 Activiti 工作流,通俗易懂

概念 工作流。通过计算机对业务流程自动化执行管理&#xff0c;主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程&#xff0c;从而实现某个预期的业务目标&#xff0c;或者促使此目标的实现”。 Activiti7 介绍 Activiti是一个工作…

Spring启动流程

Spring启动流程 按Bean状态描述&#xff1a; 创建Bean工厂对Bean工厂后置处理通过Component和Import扫描BeanDefinition&#xff0c;加入到Bean工厂注册Bean后置处理器&#xff0c;用于拦截Bean创建实例化填充属性初始化 调用aware方法BeanPostProcessor实例化前执行调用初始…

【java进阶01:final关键字】final修饰的变量只能赋一次值

目录 final修饰的类无法继承。 final修饰的引用一旦指向某个对象&#xff0c;则不能再指向其他对象&#xff0c;但该引用指向的对象内部的数据是可以修改的。​编辑 final修饰的实例变量必须手动初始化&#xff0c;因为系统不会赋默认值&#xff0c;强制手动赋值&#xff0c…

【router-view】切换组件 深刻理解用法 vue的设计思想

之前学的时候没学明白&#xff0c;导致写项目有些功能的实现上走了歪路。 今天询问了学长&#xff0c;更加深刻的理解的Vue的设计思想。 因为vue是单页面应用&#xff0c;所以学会用router-view来切换频繁变化的地方的组件是非常重要的。 之前&#xff0c;我的一个主页组件由…

Xshell远程连接配置 Ubuntu 18.04.6 + Anaconda + CUDA + Cudnn + Pytorch(GPU+CPU)

Xshell远程连接进行Ubuntu的Pytorch配置写在最前面参考Xshell常用命令Ubantu检查系统的各项配置查看ubuntu系统的版本信息查看Linux的内核版本和系统是多少位的Ubuntu版本各种验证禁用nouveau安装显卡驱动卸载显卡驱动安装显卡驱动加入PPA&#xff0c;然后更新库方法一&#xf…

maven离线模式及设置

maven离线模式及设置 maven离线模式使用场景&#xff1f; 遇到的问题&#xff1a; 最近遇到个项目支持&#xff0c;他在打jar包的时候&#xff0c;总是去网上下载 maven依赖&#xff0c;不去找我本地仓库的&#xff0c;就比较头大&#xff0c;原因不明 现在需求&#xff1a;就…

SpringBoot 玩一玩代码混淆,防止反编译代码泄露

编译 简单就是把代码跑一哈&#xff0c;然后我们的代码 .java文件 就被编译成了 .class 文件 反编译 就是针对编译生成的 jar/war 包 里面的 .class 文件 逆向还原回来&#xff0c;可以看到你的代码写的啥。 比较常用的反编译工具 JD-GUI &#xff0c;直接把编译好的jar丢进…

java项目-第144期ssm农产品供销服务系统-java毕业设计_计算机毕业设计

java项目-第144期ssm农产品供销服务系统-java毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm农产品供销服务系统》 该项目分为2个角色&#xff0c;管理员和用户。 用户可以浏览前台,包含功能有&#xff1a; 首页、农产品、农产品资讯、我的、跳转到后…

JavaScript DOM中获取元素、事件基础、操作元素、节点操作

目录 JavaScript事件三要素 常见的事件​编辑 2、执行事件的步骤&#xff1a; 1. 获取元素 1.1 方式 1.1.1 根据ID获取 1.1.2 根据标签名获取 1.1.3 通过html5新增的方法获取 1.1.4 特殊元素获取 2. 事件基础 2.1 事件三要素 2.1.1 事件源 2.1.2 事件类型 2.1.3…

如何使用Spring Security控制会话

在 Spring 安全教程的这篇文章中&#xff0c;我们将讨论Spring 安全会话管理。我们将讨论 Spring 安全性的独特功能&#xff0c;这有助于我们高效和安全的会话管理。 春季安全会议 本文将讨论安全会话管理以及 spring 安全性如何帮助我们控制 HTTP 会话。Spring 安全性使用以…

1.4_29 Axure RP 9 for mac 高保真原型图 - 案例28【中继器-后台管理系统6】功能-原位修改数据

相关链接 目录Axure中文学习网AxureShopAxureShop-QA 案例目标1. 了解使用中继器&#xff0c;弹窗修改数据的实现方式 一、成品效果 Axure Cloud 案例28【中继器 - 后台管理系统6】功能-原位修改数据 版本更新一、修改功能   1.1 文本框&#xff1a;点击数据位置&#xff…

InternImage

终于有对抗Transformer 的了~~ 来自浦江实验室、清华等机构的研究人员提出了一种新的基于卷积的基础模型&#xff0c;称为 InternImage&#xff0c;与基于 Transformer 的网络不同&#xff0c;InternImage 以可变形卷积作为核心算子&#xff0c;使模型不仅具有检测和分割等下游…