1. MyBatis插件插件机制简介

    ​ MyBatis插件其实就是为使用者提供的自行拓展拦截器,主要是为了可以更好的满足业务需要。

    ​ 在MyBatis中提供了四大核心组件对数据库进行处理,分别是Executor、Statement Handler、ParameterHandler及ResultSetHandler,同时也支持对这四大组件进行自定义扩展拦截,用来增强核心对象的功能。其本质上是使用底层的动态代理来实现的,即程序运行时执行的都是代理后的对象。

    MyBatis允许拦截的方法如下:

    • 执行器Executor (update、query、commit、rollback等方法);

    • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);

    • 参数处理器ParameterHandler (getParameterObject、setParameters方法);

    • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

  2. 拦截器参数简介

    • 拦截器注解

      @Intercepts(//可配置多个@Signature,均使用此类进行拦截增强
      {
      @Signature(//指定需要拦截的类、方法及方法参数
      type = Executor.class,//需要拦截接口
      method = "update",//需要拦截方法名称
      args = {MappedStatement.class, Object.class}//拦截方法的请求参数
      )
      }
      )
    • 实现Interceptor接口,并实现方法

      public Object intercept(Invocation invocation)//每次拦截到都会执行此方法,方法内写增强逻辑
      invocation//代理对象,可以获取目标方法、请求参数、执行结果等
      invocation.proceed() //执行目标方法
      public Object plugin(Object target)
      Plugin.wrap(target,this)//包装目标对象,为目标对象创建代理对象,将当前生成的代理对象放入拦截器链中
      public void setProperties(Properties properties)//获取配置文件中的插件参数,插件初始化时调用一次
  3. 自定义插件

    • 新建MyExecuter类,实现Interceptor接口

      package com.rangers.plugin;
      
      import org.apache.ibatis.executor.Executor;
      import org.apache.ibatis.mapping.MappedStatement;
      import org.apache.ibatis.plugin.*; import java.lang.reflect.Method;
      import java.util.Properties; /**
      * @Author Rangers
      * @Description 自定义Executor update方法
      * @Date 2021-03-11
      **/
      @Intercepts({
      @Signature(type = Executor.class,
      method = "update",
      args = {MappedStatement.class, Object.class}
      )
      })
      public class MyExecuter implements Interceptor { // 接收插件的配置参数
      private Properties properties = new Properties(); // 增强逻辑写在此方法中
      @Override
      public Object intercept(Invocation invocation) throws Throwable {
      // 打印插件的配置参数
      System.out.println("插件的配置参数:"+properties.toString());
      // 获取目标方法Method对象
      Method method = invocation.getMethod();
      // 获取目标方法的请求参数 与args列表一一对应
      String reqParam = invocation.getArgs()[1].toString();
      System.out.println("方法名称:"+method.getName()+" 请求参数:"+ reqParam);
      // 执行目标方法
      Object result = invocation.proceed();
      System.out.println("方法名称:"+method.getName()+" 执行结果:"+result);
      return result;
      } /**
      * @Author Rangers
      * @Description
      **/
      @Override
      public Object plugin(Object target) {
      //System.out.println("需要包装的目标对象:"+target+" 目标对象类型"+ target.getClass());
      // 主要是将当前生成的代理对象放入拦截器链中,包装目标对象,为目标对象创建代理对象
      return Plugin.wrap(target,this);
      } /**
      * @Author Rangers
      * @Description 获取配置文件中的插件属性参数,插件初始化时调用一次
      **/
      @Override
      public void setProperties(Properties properties) {
      // 将配置参数进行接收
      this.properties = properties;
      }
      }
    • 主配置文件添加标签

      <plugins>
      <!--指定拦截器类-->
      <plugin interceptor="com.rangers.plugin.MyExecuter">
      <!--配置拦截器 属性参数-->
      <property name="param1" value="value1"/>
      <property name="param2" value="value2"/>
      <property name="param3" value="value3"/>
      </plugin>
      </plugins>
  4. 插件原理

    a、在Executor、StatementHandler、ParameterHandler及ResultSetHandler四大对象创建时,并不是直接返回的,而是中间多了一步interceptorChain.pluginAll()(均在Configuration类中进行创建)。

    • Executor—interceptorChain.pluginAll(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) {
    executor = new ReuseExecutor(this, transaction);
    } else {
    executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
    executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
    }
    • StatementHandler—interceptorChain.pluginAll(statementHandler);
    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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
    }
    • ParameterHandler—interceptorChain.pluginAll(parameterHandler);
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
    }
    • ResultSetHandler—interceptorChain.pluginAll(resultSetHandler)
    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
    ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
    }

    b、interceptorChain.pluginAll()调用的就是实现了Interceptor接口的plugin()方法,plugin()方法又通过Plugin.wrap(target,this)为目标对象创建一个Plugin的代理对象,添加到拦截链interceptorChain中。具体代码如下:

      public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
    // 调用实现Interceptor接口的plugin方法
    target = interceptor.plugin(target);
    }
    return target;
    }
    @Override
    public Object plugin(Object target) {
    // 调用Plugin的wrap()方法,创建代理对象
    return Plugin.wrap(target,this);
    }
    public static Object wrap(Object target, Interceptor 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;
    }

    c、Plugin实现了 InvocationHandler接口,因此它的invoke方法会拦截所有的方法调用。invoke()方法会 对所拦截的方法进行检测,以决定是否执行插件逻辑。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    // 根据组件对象Class从signatureMap中获取到需要拦截的方法set集合
    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);
    }
    }
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // 获取到所有拦截器的类
    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();
    // signatureMap存放所有拦截到方法,key为四大组件的Class,value为组件对应的方法set集合
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
    Set<Method> methods = signatureMap.get(sig.type());
    if (methods == null) {
    methods = new HashSet<Method>();
    signatureMap.put(sig.type(), methods);
    }
    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;
    }
  5. PageHelper插件

    PageHelper是MyBaits框架使用最广泛的第三方物理分页插件,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

    使用步骤:

    • 添加依赖

      <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.8</version>
      </dependency>
      <dependency>
      <groupId>com.github.jsqlparser</groupId>
      <artifactId>jsqlparser</artifactId>
      <version>1.2</version>
      </dependency>
    • 配置插件

      <plugin interceptor="com.github.pagehelper.PageInterceptor">
      </plugin>
    • 使用分页

      @org.junit.Test
      public void testPagehealper() {
      PageHelper.startPage(1, 2);
      List<User> users = userDao.findAll();
      if (users != null && users.size() > 0) {
      for (User user : users) {
      System.out.println(user.toString());
      }
      PageInfo<User> pageInfo = new PageInfo<>(users);
      System.out.println("总条数:" + pageInfo.getTotal());
      System.out.println("总页数:" + pageInfo.getPages());
      System.out.println("当前页:" + pageInfo.getPageNum());
      System.out.println("每页显万长度:" + pageInfo.getPageSize());
      System.out.println("是否第一页:" + pageInfo.isIsFirstPage());
      System.out.println("是否最后一页:" + pageInfo.isIsLastPage());
      }
      }

MyBatis(八):MyBatis插件机制详解的更多相关文章

  1. Maven使用教程三:maven的生命周期及插件机制详解

    前言 今天这个算是学习Maven的一个收尾文章,里面内容不局限于标题中提到的,后面还加上了公司实际使用的根据profile配置项目环境以及公司现在用的archetype 模板等例子. 后面还会总结一个 ...

  2. 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)

    文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...

  3. 《深入理解mybatis原理2》 Mybatis初始化机制详解

    <深入理解mybatis原理> Mybatis初始化机制详解 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程 ...

  4. Quartz学习——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成详解(转)

    通过前面的学习,你可能大致了解了Quartz,本篇博文为你打开学习SSMM+Quartz的旅程!欢迎上车,开始美好的旅程! 本篇是在SSM框架基础上进行的. 参考文章: 1.Quartz学习——Qua ...

  5. 《深入理解mybatis原理6》 MyBatis的一级缓存实现详解 及使用注意事项

    <深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 0.写在前面   MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓 ...

  6. [转载]Mybatis Generator最完整配置详解

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration ...

  7. maven常用插件配置详解

    常用插件配置详解Java代码    <!-- 全局属性配置 --> <properties> <project.build.name>tools</proje ...

  8. ssion机制详解

    ssion机制详解   ref:http://justsee.iteye.com/blog/1570652 虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚sess ...

  9. hibernate缓存机制详解

    hiberante面试题—hibernate缓存机制详解   这是面试中经常问到的一个问题,可以按照我的思路回答,准你回答得很完美.首先说下Hibernate缓存的作用(即为什么要用缓存机制),然后再 ...

随机推荐

  1. 在 Spring Boot 2 中致敬 JSP

    新冠病毒

  2. C# LINQ (2)

    Limiting Data -- Take() and Skip() 前面讲了 筛选 和 排序,现在讲 选取皇帝选妃,层层选拔,最后留几个,让他过目,他选一个或者几个作为妃子,大概是这么个意思Take ...

  3. 深入剖析JavaScript中的数据类型判断(typeof instanceof prototype.constructor)

    关于JavaScript中的类型判断,我想大部分JavaScripter 都很清楚 typeof 和  instanceof,却很少有人知道 constructor,以及constructor与前面二 ...

  4. 重学c#————struct

    前言 简单整理一下struct. 正文 struct 对于struct 而言呢,我们往往会拿class作为对比,但是呢,我们在初学阶段用class来替代struct,struct的存在感越来越低了. ...

  5. ARM汇编--汇编中符号和变量

    习惯了使用C语言的情况下我发现自己对与汇编程序的符号和变量的理解很不深刻,今天抽空来学学加深理解.以ARM汇编来说,在汇编代码中所有以"."开头的指令都是汇编伪指令,他们不属于AR ...

  6. 图片转tfrecords

    import numpy as np import tensorflow as tf import time import os import cv2 from sklearn.utils impor ...

  7. 20 个使用原生 JavaScript 实现的 Web 项目

    20 个使用原生 JavaScript 实现的 Web 项目 20 vanilla JavaScript Web Projects https://github.com/learning-js-by- ...

  8. HTML5 + JS 网站追踪技术:帆布指纹识别 Canvas FingerPrinting Universally Unique Identifier,简称UUID

    1 1 1 HTML5 + JS  网站追踪技术:帆布指纹识别 Canvas FingerPrinting 1 一般情况下,网站或者广告联盟都会非常想要一种技术方式可以在网络上精确定位到每一个个体,这 ...

  9. React Hooks 内部实现原理

    React Hooks 内部实现原理 源码分析 // 链表 React Hooks 原理剖析 refs https://reactjs.org/docs/hooks-intro.html https: ...

  10. c++ winapi 在当前程序(local)调用目标程序(target)的函数

    GameCheat stackoverflow 如果你的目标程序是x86/x64, 那么当前程序也需要编译为x84/x64 #include <iostream> #include < ...