MyBatis插件机制超详细讲解

目录
  • MyBatis的插件机制
    • InterceptorChain
    • MyBatis中的Plugin
  • MyBatis插件开发
  • 总结

MyBatis的插件机制

MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

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

这里我们再回顾一下,在创建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;
 }

InterceptorChain

在全局配置Configuration中维护了一个InterceptorChain interceptorChain = new InterceptorChain()拦截器链,其内部维护了一个私有常量List<Interceptor> interceptors,其pluginAll方法为遍历interceptors并调用每个拦截器的plugin方法。

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  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);
  }
}

MyBatis中拦截器接口

package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
  //拦截处理,也就是代理对象目标方法执行前被处理
  Object intercept(Invocation invocation) throws Throwable;
  //生成代理对象
  Object plugin(Object target);
  //设置属性
  void setProperties(Properties properties);
}

如果想自定义插件,那么就需要实现该接口。

MyBatis中的Invocation

package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Invocation {
 //目标对象
 private final Object target;
 //目标对象的方法
 private final Method method;
 //方法参数
 private final Object[] args;
 public Invocation(Object target, Method method, Object[] args) {
   this.target = target;
   this.method = method;
   this.args = args;
 }
 public Object getTarget() { return target;}
 public Method getMethod() {return method;}
 public Object[] getArgs() {return args;}
 //proceed-继续,也就是说流程继续往下执行,这里看方法就是目标方法反射调用。
 public Object proceed() throws InvocationTargetException, IllegalAccessException {
   return method.invoke(target, args);
 }
}

MyBatis中的Plugin

package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
public class Plugin implements InvocationHandler {
  //目标对象 ,被代理的对象
  private final Object target;
  //拦截器
  private final Interceptor interceptor;
  //方法签名集合
  private final Map<Class<?>, Set<Method>> signatureMap;
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
  //该方法会获取signatureMap中包含的所有target实现的接口,然后生成代理对象
  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;
  }
  //每一个InvocationHandler 的invoke方法会在代理对象的目标方法执行前被触发
  @Override
  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)) {
        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[] 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;
  }
	//该方法会获取signatureMap中包含的所有type实现的接口与上级接口
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

这里Plugin实现了InvocationHandler,那么其invoke方法会在代理对象的目标方法执行前被触发。其invoke方法解释如下:

  • ① 获取当前Plugin感兴趣的方法类型,判断目标方法Method是否被包含;
  • ② 如果当前目标方法是Plugin感兴趣的,那么就interceptor.intercept(new Invocation(target, method, args));触发拦截器的intercept方法;
  • ③ 如果当前目标方法不是Plugin感兴趣的,直接执行目标方法。

上面说Plugin感兴趣其实是指内部的interceptor感兴趣。

MyBatis插件开发

如下所示,编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名。

package com.mybatis.dao;
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
/**
 * 完成插件签名:告诉MyBatis当前插件用来拦截哪个对象的哪个方法
 */
@Intercepts({@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
public class MyFirstPlugin implements Interceptor{
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
		Object target = invocation.getTarget();
		System.out.println("当前拦截到的对象:"+target);
		//拿到:StatementHandler==>ParameterHandler===>parameterObject
		//拿到target的元数据
		MetaObject metaObject = SystemMetaObject.forObject(target);
		Object value = metaObject.getValue("parameterHandler.parameterObject");
		System.out.println("sql语句用的参数是:"+value);
		//修改完sql语句要用的参数
		metaObject.setValue("parameterHandler.parameterObject", 11);
		//执行目标方法
		Object proceed = invocation.proceed();
		//返回执行后的返回值
		return proceed;
	}
	 //plugin:包装目标对象的:包装:为目标对象创建一个代理对象
	@Override
	public Object plugin(Object target) {
		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
		Object wrap = Plugin.wrap(target, this);
		//返回为当前target创建的动态代理
		return wrap;
	}
	 //setProperties:将插件注册时 的property属性设置进来
	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		System.out.println("插件配置的信息:"+properties);
	}
}

注册到mybatis的全局配置文件中,示例如下(注意,插件是可以设置属性的如这里我们可以设置用户名、密码):

<plugins>
    <plugin interceptor="com.mybatis.dao.MyFirstPlugin">
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </plugin>
</plugins>

那么mybatis在执行过程中实例化Executor、ParameterHandler、ResultSetHandler和StatementHandler时都会触发上面我们自定义插件的plugin方法。

如果有多个插件,那么拦截器链包装的时候会从前到后,执行的时候会从后到前。如这里生成的StatementHandler代理对象如下:

总结

  • 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理;
  • 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹,形成代理链;
  • 目标方法执行时依次从外到内执行插件的intercept方法。
  • 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值

到此这篇关于MyBatis插件机制超详细讲解的文章就介绍到这了,更多相关MyBatis插件机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MyBatis Plus插件机制与执行流程原理分析详解

    MyBatis Plus插件 MyBatis Plus提供了分页插件PaginationInterceptor.执行分析插件SqlExplainInterceptor.性能分析插件PerformanceInterceptor以及乐观锁插件OptimisticLockerInterceptor. Mybatis 通过插件 (Interceptor) 可以做到拦截四大对象相关方法的执行 ,根据需求完成相关数据的动态改变. 四大对象是: Executor StatementHandler Parame

  • mybatis的插件机制详解

    前言 Mybatis作为一个应用广泛的优秀的ORM框架,已经成了JavaWeb世界近乎标配的部分,这个框架具有强大的灵活性,在四大组件(Executor.StatementHandler.ParameterHandler.ResultSetHandler)处提供了简单易用的插件扩展机制.Mybatis对持久层的操作就是借助于四大核心对象.MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说

  • MyBatis-Plus插件机制及通用Service新功能

    目录 1.高级(插件机制) 1.1自动填充 1.1.1 原理 1.1.2 基本操作 1.2乐观锁 1.2.1 什么是乐观锁 1.2.2. 实现 1.2.3 注意事项 1.3逻辑删除 1.3.1 什么是逻辑删除 1.3.2 实现 1.3.3 注意 1.3.4 全局配置 1.3.5 恢复 2.通用Service 2.1分析 通用Service分析 2.2基本使用 标准service:接口 + 实现 2.3常见方法 3.新功能 3.1执行SQL分析打印 3.2数据库安全保护 1.高级(插件机制) 1.

  • 浅谈myBatis中的插件机制

    插件的配置与使用 在mybatis-config.xml配置文件中配置plugin结点,比如配置一个自定义的日志插件LogInterceptor和一个开源的分页插件PageInterceptor: <plugins> <plugin interceptor="com.crx.plugindemo.LogInterceptor"></plugin> <plugin interceptor="com.github.pagehelper.P

  • MyBatis插件机制超详细讲解

    目录 MyBatis的插件机制 InterceptorChain MyBatis中的Plugin MyBatis插件开发 总结 MyBatis的插件机制 MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler(

  • React运行机制超详细讲解

    目录 适合人群 写源码之前的必备知识点 JSX 虚拟Dom 原理简介 手写react过程 基本架子的搭建 React的源码 ReactDom.render ReactDom.Component 简单源码 适合人群 本文适合0.5~3年的react开发人员的进阶. 讲讲废话: react的源码,的确是比vue的难度要深一些,本文也是针对初中级,本意了解整个react的执行过程. 写源码之前的必备知识点 JSX 首先我们需要了解什么是JSX. 网络大神的解释:React 使用 JSX 来替代常规的

  • React渲染机制超详细讲解

    目录 准备工作 render阶段 workloopSync beginWork completeWork commit阶段 commitWork mutation之前 mutation fiber树切换 layout layout之后 总结 准备工作 为了方便讲解,假设我们有下面这样一段代码: function App(){ const [count, setCount] = useState(0) useEffect(() => { setCount(1) }, []) const handl

  • Python多进程并发与同步机制超详细讲解

    目录 多进程 僵尸进程 Process类 函数方式 继承方式 同步机制 状态管理Managers 在<多线程与同步>中介绍了多线程及存在的问题,而通过使用多进程而非线程可有效地绕过全局解释器锁. 因此,通过multiprocessing模块可充分地利用多核CPU的资源. 多进程 多进程是通过multiprocessing包来实现的,multiprocessing.Process对象(和多线程的threading.Thread类似)用来创建一个进程对象: 在类UNIX平台上,需要对每个Proce

  • JavaGUI事件监听机制超详细讲解

    1.一个事件模型中有上对象:事件源,事件以及监听程序 2.事件监听机制: 事件源        事件发生的地方 事件            要发生的事情 事件处理     针对发生的事情做出的处理方案 事件监听     把事件源和事件关联起来 使用步骤: 新建一个组件(如 JButton) 将该组件添加到相应的面板(如 JFrame) 注册监听器以监听事件源产生的事件(如通过ActionListener来响应用户点击按钮) 定义处理事件的方法(如在ActionListener中的actionPe

  • MyBatis 超详细讲解动态SQL的实现

    目录 情景: 概述: SQL元素: <if>: <choose>: <where>: <trim>: <set>: <foreach>: <bind>: 情景: 我们在使用JDBC或者其他类似的框架开发进行数据库开发时,通常要根据需求动手组装SQL,不用想都觉得是件很痛苦的事情了,而Mybatis框架提供的对SQL语句动态组装的功能,能很好地解决这个麻烦. 概述: 动态SQL是MyBatis框架的一个强大特性,MyBati

  • Java 超详细讲解Spring MVC异常处理机制

    目录 异常处理机制流程图 异常处理的两种方式 简单异常处理器SimpleMappingExceptionResolver 自定义异常处理步骤 本章小结 异常处理机制流程图 系统中异常包括两类: 预期异常 通过捕获异常从而获取异常信息. 运行时异常RuntimeException 主要通过规范代码开发.测试等手段减少运行时异常的发生. 系统的Dao.Service.Controller出现都通过throws Exception向上抛出,最后SpringMVC前端控制器交由异常处理器进行异常处理,如

  • MyBatis 超详细讲解动态SQL的实现

    目录 情景: 概述: SQL元素: <if>: <choose>: <where>: <trim>: <set>: <foreach>: <bind>: 情景: 我们在使用JDBC或者其他类似的框架开发进行数据库开发时,通常要根据需求动手组装SQL,不用想都觉得是件很痛苦的事情了,而Mybatis框架提供的对SQL语句动态组装的功能,能很好地解决这个麻烦. 概述: 动态SQL是MyBatis框架的一个强大特性,MyBati

  • MyBatis超详细讲解如何实现分页功能

    目录 概述: 一般用法: student表: Student.java StudentMapper.xml StudentService.java StudentServiceImpl.java AppTest.java 测试结果: 使用分页参数RowBound StudentMapper.xml 概述: 当希望能直接在数据库语言中只检索符合条件的记录,不需要再通过程序对其做处理时,SQL语句分页技术便横空出世了,通过SQL语句实现分页只需要改变查询语句就能实现,即在SQL语句后面添加limit

  • Mybatis超详细讲解构建SQL方法

    目录 1 SQL 构建对象介绍 1.1.1 介绍 1.1.2 实现准备 2 查询功能的实现 3 新增功能的实现 4 修改功能的实现 5 删除功能的实现 1 SQL 构建对象介绍 1.1.1 介绍 我们之前通过注解开发时,相关 SQL 语句都是自己直接拼写的.一些关键字写起来比较麻烦.而且容易出错. MyBatis 给我们提供了 org.apache.ibatis.jdbc.SQL 功能类,专门用于构建 SQL 语句 1.1.2 实现准备 编写指定调用方法 package com.yyl.sql;

随机推荐