简单了解mybatis拦截器实现原理及实例

这篇文章主要介绍了简单了解mybatis拦截器实现原理及实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

例行惯例,先看些基本概念:

1 拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑

2 Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。

自定义拦截器

/**
 * mybatis 自定义拦截器
 * 三步骤:
 * 1 实现 {@link Interceptor} 接口
 * 2 添加拦截注解 {@link Intercepts}
 * 3 配置文件中添加拦截器
 *
 * 1 实现 {@link Interceptor} 接口
 *   具体作用可以看下面代码每个方法的注释
 * 2 添加拦截注解 {@link Intercepts}
 *   mybatis 拦截器默认可拦截的类型只有四种,即四种接口类型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
 *   对于我们的自定义拦截器必须使用 mybatis 提供的注解来指明我们要拦截的是四类中的哪一个类接口
 *   具体规则如下:
 *     a:Intercepts 拦截器: 标识我的类是一个拦截器
 *     b:Signature 署名: 则是指明我们的拦截器需要拦截哪一个接口的哪一个方法
 *       type  对应四类接口中的某一个,比如是 Executor
 *       method 对应接口中的哪类方法,比如 Executor 的 update 方法
 *       args  对应接口中的哪一个方法,比如 Executor 中 query 因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法
 * 3 配置文件中添加拦截器
 *   拦截器其实就是一个 plugin,在 mybatis 核心配置文件中我们需要配置我们的 plugin :
 *     <plugin interceptor="liu.york.mybatis.study.plugin.MyInterceptor">
 *       <property name="username" value="LiuYork"/>
 *       <property name="password" value="123456"/>
 *     </plugin>
 *
 * 拦截器顺序
 * 1 不同拦截器顺序:
 *   Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
 *
 * 2 对于同一个类型的拦截器的不同对象拦截顺序:
 *   在 mybatis 核心配置文件根据配置的位置,拦截顺序是 从上往下
 */
@Intercepts({
    @Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
    @Signature(method = "query", type = StatementHandler.class, args = {Statement.class, ResultHandler.class})
})
public class MyInterceptor implements Interceptor {

  /**
   * 这个方法很好理解
   * 作用只有一个:我们不是拦截方法吗,拦截之后我们要做什么事情呢?
   *   这个方法里面就是我们要做的事情
   *
   * 解释这个方法前,我们一定要理解方法参数 {@link Invocation} 是个什么鬼?
   * 1 我们知道,mybatis拦截器默认只能拦截四种类型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
   * 2 不管是哪种代理,代理的目标对象就是我们要拦截对象,举例说明:
   *   比如我们要拦截 {@link Executor#update(MappedStatement ms, Object parameter)} 方法,
   *   那么 Invocation 就是这个对象,Invocation 里面有三个参数 target method args
   *     target 就是 Executor
   *     method 就是 update
   *     args  就是 MappedStatement ms, Object parameter
   *
   *  如果还是不能理解,我再举一个需求案例:看下面方法代码里面的需求
   *
   * 该方法在运行时调用
   */
  @Override
  public Object intercept(Invocation invocation) throws Throwable {

    /*
     * 需求:我们需要对所有更新操作前打印查询语句的 sql 日志
     * 那我就可以让我们的自定义拦截器 MyInterceptor 拦截 Executor 的 update 方法,在 update 执行前打印sql日志
     * 比如我们拦截点是 Executor 的 update 方法 : int update(MappedStatement ms, Object parameter)
     *
     * 那当我们日志打印成功之后,我们是不是还需要调用这个query方法呢,如何如调用呢?
     * 所以就出现了 Invocation 对象,它这个时候其实就是一个 Executor,而且 method 对应的就是 query 方法,我们
     * 想要调用这个方法,只需要执行 invocation.proceed()
     */

    /* 因为我拦截的就是Executor,所以我可以强转为 Executor,默认情况下,这个Executor 是个 SimpleExecutor */
    Executor executor = (Executor)invocation.getTarget();

    /*
     * Executor 的 update 方法里面有一个参数 MappedStatement,它是包含了 sql 语句的,所以我获取这个对象
     * 以下是伪代码,思路:
     * 1 通过反射从 Executor 对象中获取 MappedStatement 对象
     * 2 从 MappedStatement 对象中获取 SqlSource 对象
     * 3 然后从 SqlSource 对象中获取获取 BoundSql 对象
     * 4 最后通过 BoundSql#getSql 方法获取 sql
     */
    MappedStatement mappedStatement = ReflectUtil.getMethodField(executor, MappedStatement.class);
    SqlSource sqlSource = ReflectUtil.getField(mappedStatement, SqlSource.class);
    BoundSql boundSql = sqlSource.getBoundSql(args);
    String sql = boundSql.getSql();
    logger.info(sql);

    /*
     * 现在日志已经打印,需要调用目标对象的方法完成 update 操作
     * 我们直接调用 invocation.proceed() 方法
     * 进入源码其实就是一个常见的反射调用 method.invoke(target, args)
     * target 对应 Executor对象
     * method 对应 Executor的update方法
     * args  对应 Executor的update方法的参数
     */

    return invocation.proceed();
  }

  /**
   * 这个方法也很好理解
   * 作用就只有一个:那就是Mybatis在创建拦截器代理时候会判断一次,当前这个类 MyInterceptor 到底需不需要生成一个代理进行拦截,
   * 如果需要拦截,就生成一个代理对象,这个代理就是一个 {@link Plugin},它实现了jdk的动态代理接口 {@link InvocationHandler},
   * 如果不需要代理,则直接返回目标对象本身
   *
   * Mybatis为什么会判断一次是否需要代理呢?
   * 默认情况下,Mybatis只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
   * 通过 {@link Intercepts} 和 {@link Signature} 两个注解共同完成
   * 试想一下,如果我们开发人员在自定义拦截器上没有指明类型,或者随便写一个拦截点,比如Object,那Mybatis疯了,难道所有对象都去拦截
   * 所以Mybatis会做一次判断,拦截点看看是不是这四个接口里面的方法,不是则不拦截,直接返回目标对象,如果是则需要生成一个代理
   *
   * 该方法在 mybatis 加载核心配置文件时被调用
   */
  @Override
  public Object plugin(Object target) {
    /*
     * 看了这个方法注释,就应该理解,这里的逻辑只有一个,就是让mybatis判断,要不要进行拦截,然后做出决定是否生成一个代理
     *
     * 下面代码什么鬼,就这一句就搞定了?
     * Mybatis判断依据是利用反射,获取这个拦截器 MyInterceptor 的注解 Intercepts和Signature,然后解析里面的值,
     * 1 先是判断要拦截的对象是四个类型中 Executor、StatementHandler、ParameterHandler、 ResultSetHandler 的哪一个
     * 2 然后根据方法名称和参数(因为有重载)判断对哪一个方法进行拦截 Note:mybatis可以拦截这四个接口里面的任一一个方法
     * 3 做出决定,是返回一个对象呢还是返回目标对象本身(目标对象本身就是四个接口的实现类,我们拦截的就是这四个类型)
     *
     * 好了,理解逻辑我们写代码吧~~~ What !!! 要使用反射,然后解析注解,然后根据参数类型,最后还要生成一个代理对象
     * 我一个小白我怎么会这么高大上的代码嘛,怎么办?
     *
     * 那就是使用下面这句代码吧 哈哈
     * mybatis 早就考虑了这里的复杂度,所以提供这个静态方法来实现上面的逻辑
     */
    return Plugin.wrap(target, this);
  }

  /**
   * 这个方法最好理解,如果我们拦截器需要用到一些变量参数,而且这个参数是支持可配置的,
   * 类似Spring中的@Value("${}")从application.properties文件获取
   * 这个时候我们就可以使用这个方法
   *
   * 如何使用?
   * 只需要在 mybatis 配置文件中加入类似如下配置,然后 {@link Interceptor#setProperties(Properties)} 就可以获取参数
   *   <plugin interceptor="liu.york.mybatis.study.plugin.MyInterceptor">
   *      <property name="username" value="LiuYork"/>
   *      <property name="password" value="123456"/>
   *   </plugin>
   *   方法中获取参数:properties.getProperty("username");
   *
   * 问题:为什么要存在这个方法呢,比如直接使用 @Value("${}") 获取不就得了?
   * 原因是 mybatis 框架本身就是一个可以独立使用的框架,没有像 Spring 这种做了很多依赖注入的功能
   *
   * 该方法在 mybatis 加载核心配置文件时被调用
   */
  @Override
  public void setProperties(Properties properties) {
    String username = properties.getProperty("username");
    String password = properties.getProperty("password");
    // TODO: 2019/2/28 业务逻辑处理...
  }
}

三个核心方法都加了详细的注释,而且结合案例需求说明问题

那么多文字不想行看,没关系有概括

总结:

1.在mybatis中可被拦截的类型有四种(按照拦截顺序):

  • Executor:拦截执行器的方法。
  • ParameterHandler:拦截参数的处理。
  • ResultHandler:拦截结果集的处理。
  • StatementHandler:拦截Sql语法构建的处理。

2.各个参数的含义:

  • @Intercepts:标识该类是一个拦截器;
  • @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;

2.1 type:对应四种类型中的一种;

2.2 method:对应接口中的哪类方法(因为可能存在重载方法);

2.3 args:对应哪一个方法;

不知道能否帮助你理解,我的表达能力有限~~~

接下来我们看看 Plugin 类

package org.apache.ibatis.plugin;

/**
 * Plugin 类其实就是一个代理类,因为它实现了jdk动态代理接口 InvocationHandler
 * 我们核心只需要关注两个方法
 * wrap:
 *   如果看懂了代码案例1的例子,那么这个方法很理解,这个方法就是 mybatis 提供给开发人员使用的一个工具类方法,
 *   目的就是帮助开发人员省略掉 反射解析注解 Intercepts 和 Signature,有兴趣的可以去看看源码 Plugin#getSignatureMap 方法
 *
 * invoke:
 *   这个方法就是根据 wrap 方法的解析结果,判断当前拦截器是否需要进行拦截,
 *   如果需要拦截:将 目标对象+目标方法+目标参数 封装成一个 Invocation 对象,给我们自定义的拦截器 MyInterceptor 的 intercept 方法
 *          这个时候就刚好对应上了上面案例1中对 intercept 方法的解释了,它就是我们要处理自己逻辑的方法,
 *          处理好了之后是否需要调用目标对象的方法,比如上面说的 打印了sql语句,是否还要查询数据库呢?答案是肯定的
 *   如果不需要拦截:则直接调用目标对象的方法
 *          比如直接调用 Executor 的 update 方法进行更新数据库
 *
 */
class Plugin implements InvocationHandler {

  public static Object wrap(Object target, Interceptor interceptor) {
    // 省略
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 省略
  }
}

贴一段网上的通用解释吧:

Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。

所以接着我们来看一下该invoke方法的内容。这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

这就是Mybatis中实现Interceptor拦截的一个思想

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Mybatis拦截器的实现介绍

     MyBatis介绍 MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .它支持普通 SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索.MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的

  • MyBatis拦截器实现分页功能实例

    由于业务关系 巴拉巴拉巴拉 好吧 简单来说就是 原来的业务是 需要再实现类里写 selectCount 和selectPage两个方法才能实现分页功能 现在想要达到效果是 只通过一个方法就可以实现 也就是功能合并 所以就有了下面的实践 既然是基于MyBatis 所以就先搭建一个Mybatis的小项目 1.01导入 mybatis和mysql的包 1.02.配置文件 Configuration.xml 中添加 <environments default="development"&

  • Mybatis拦截器实现分页

    最终dao层结果: public interface ModelMapper { Page<Model> pageByConditions(RowBounds rowBounds, Model record); } 接下来一步一步来实现分页. 一.创建Page对象: public class Page<T> extends PageList<T> { private int pageNo = 1;// 页码,默认是第一页 private int pageSize = 1

  • java利用mybatis拦截器统计sql执行时间示例

    可以根据执行时间打印sql语句,打印的sql语句是带参数的,可以拷贝到查询分析器什么的直接运行 复制代码 代码如下: package mybatis; import java.text.DateFormat;import java.util.Date;import java.util.List;import java.util.Locale;import java.util.Properties; import org.apache.ibatis.executor.Executor;import

  • mybatis拦截器与分页插件实例教程

    mybatis介绍 拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法. MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型.接口和 Java 的 POJO(Plain Old Java Objects,普

  • MyBatis拦截器实现分页功能的实现方法

    MyBatis拦截器实现分页功能的实现方法 前言: 首先说下实现原理.使用拦截器拦截原始的sql,然后加上分页查询的关键字和属性,拼装成新的sql语句再交给mybatis去执行. 除了业务代码之外,需要写的东西不多,提几个关键的: 1.分页对象Page类.给该对象设置一个当前页数(前端给).总记录数(拦截器内赋值)2个参数,他就能帮你计算出分页sql语句用的2个参数. /** * 分页对应的实体类 */ public class Page { /** * 总条数 */ private int t

  • MyBatis Excutor 拦截器的巧妙用法

    这里要讲的巧妙用法是用来实现在拦截器中执行额外 MyBatis 现有方法的用法. 并且会提供一个解决拦截Executor时想要修改MappedStatement时解决并发的问题. 这里假设一个场景: 实现一个拦截器,记录 MyBatis 所有的 insert,update,delete 操作,将记录的信息存入数据库. 这个用法在这里就是将记录的信息存入数据库. 实现过程的关键步骤和代码: 1.首先在某个 Mapper.xml 中定义好了一个往日志表中插入记录的方法,假设方法为id="insert

  • MyBatis拦截器:给参数对象属性赋值的实例

    该拦截器的作用:在进行增加.修改等操作时,给数据模型的一些通用操作属性(如:创建人.创建时间.修改人.修改时间等)自动赋值. 该实现是在DAO层拦截,即存入DB前最后一层.后经分析,不是很合理,改为在service层拦截,用spring AOP来实现了,该代码遂弃用.不过已经测试可用,记录备忘. package com.development; import java.lang.reflect.InvocationTargetException; import java.util.Date; i

  • Mybatis Interceptor 拦截器的实现

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件. 拦截器(Interceptor)在 Mybatis 中被当做插件(plugin)对待,官方文档提供了 Executor,ParameterHandler,ResultSetHandler,StatementHandler 共4种,并且提示"这些类中方法的细

  • 简单了解mybatis拦截器实现原理及实例

    这篇文章主要介绍了简单了解mybatis拦截器实现原理及实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 例行惯例,先看些基本概念: 1 拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑 2 Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑. 自定义拦截器 /** * mybatis 自定义拦截器 * 三步骤: * 1 实现 {@link Intercept

  • MyBatis拦截器的原理与使用

    目录 二.拦截器注册的三种方式 1.XML注册 2.配置类注册 3.注解方式 三.ParameterHandler参数改写-修改时间和修改人统一插入 四.通过StatementHandler改写SQL 一.拦截对象和接口实现示例 MyBatis拦截器的作用是在于Dao到DB中间进行额外的处理.大部分情况下通过mybatis的xml配置sql都可以达到想要的DB操作效果,然而存在一些类似或者相同的查询条件或者查询要求,这些可以通过拦截器的实现可以提升开发效率,比如:分页.插入和更新时间/人.数据权

  • MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) P

  • MyBatis拦截器的实现原理

    目录 前言 1.使用方法 2.MyBatis对象的创建 3.代理对象的创建 3.1 拦截器的获取 3.2 代理对象的创建 4. 拦截器的执行过程 5. 拦截器的执行顺序 前言 Mybatis拦截器并不是每个对象里面的方法都可以被拦截的.Mybatis拦截器只能拦截Executor.StatementHandler.ParameterHandler.ResultSetHandler四个类里面的方法,这四个对象在创建的时候才会创建代理. 用途:实际工作中,可以使用Mybatis拦截器来做一些SQL权

  • java MyBatis拦截器Inteceptor详细介绍

    有许多java初学者对于MyBatis拦截器Inteceptor不是很了解,在这里我来为各位整理下篇关于java中MyBatis拦截器Inteceptor详解, 本文主要分析MyBatis的插件机制,实际就是Java动态代理实现的责任链模式实现. 根据官方文档.Mybatis只允许拦截以下方法,这个决定写拦截器注解签名参数. 代码如下 Executor (update, query, flushStatements, commit, rollback, getTransaction, close

  • Java web拦截器inteceptor原理及应用详解

    这篇文章主要介绍了java web拦截器inteceptor原理及应用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.简介 java里的拦截器提供的是非系统级别的拦截,也就是说,就覆盖面来说,拦截器不如过滤器强大,但是更有针对性. Java中的拦截器是基于Java反射机制实现的,更准确的划分,应该是基于JDK实现的动态代理.它依赖于具体的接口,在运行期间动态生成字节码. 拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发

随机推荐