mybatis之插件机制

本篇来聊一下mybatis的插件机制,基于myabtis 3.4.6版本。
知识点

  • 什么是插件
  • 如何自定义插件
  • 插件实现原理
什么是插件
相信大家平时肯定使用过很多插件,比如eclipse插件、idea的插件、chrome浏览器插件等,简单地说,插件就是一种扩展,它不属于核心功能,没有它不影响我们正常使用,有了它能够让我们使用起来更方便,属于锦上添花。以下引用知乎一篇文章部分内容:
主程序如果希望自身的功能可以被扩展,其需要:
  1. 提供一组服务 (Service Interface)。其提供(约束)了插件对主体能力可控制的边界。服务定义的越精细,插件控制的粒度越小,能力越大,但耦合度和复杂度也越高。
  2. 定义一种扩展契约 (Plug-In Interface),其描述了插件应该如何设计以便于主程序发现。并通过插件管理模块 (Plug-In Manager) 来发现、维护插件。插件通过实现主程序规定的扩展契约(通常是一个接口),标明自己的身份,并接收来自主程序的事件响应。通过调动主程序提供的服务,实现和主程序的交互。这一过程,通常都是被主程序以 SDK (Software Development Kit) 的形式封装。
    mybatis之插件机制
    文章图片

    一款好的产品必然有一套灵活的扩展机制,作为目前国内互联网公司用得最多的持久化框架,mybatis 自然也提供了一套扩展机制,也就是插件。
如何自定义插件
在介绍完插件的概念之后,我们来看一下如何在 mybatis 中自定义插件。目前 mybatis 支持4种类型的插件,分别为 Executor、StatementHandler、ResultSetHandler、ParameterHandler。什么意思呢?就是说我们目前自定义插件,只能对这4种类型的方法做拦截,这里基本上涵盖了参数化、sql执行前后、结果集处理,mybatis核心逻辑也就这么多了,所以是足够用的。下面我们来自定义一个插件,实现对Executor的拦截,在查询之前打印"hello"。
1) 先定义一个类,实现拦截器功能
public class MyPlugin implements Interceptor {@Override public Object intercept(Invocation invocation) throws Throwable { return null; }@Override public Object plugin(Object o) { return null; }@Override public void setProperties(Properties properties) { } }

2)由于我们要对 Executor 的查询进行拦截,所以在其上添加对应的拦截注解说明,当然这里是可以拦截多种接口的
@Intercepts( value = https://www.it610.com/article/{ @Signature(type = Executor.class, method ="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) } ) public class MyPlugin implements Interceptor { ... }

3) 加一个Properties类型成员用来接收配置中设置的值,并在执行具体路基之前输出配置中对应的内容,当然你也可以直接写死输出hello
@Intercepts( value = https://www.it610.com/article/{ @Signature(type = Executor.class, method ="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) } ) public class MyPlugin implements Interceptor { private Properties properties = new Properties(); @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println(properties.getProperty("output")); return invocation.proceed(); }@Override public Object plugin(Object o) { return Plugin.wrap(o, this); }@Override public void setProperties(Properties properties) { this.properties = properties; } }

4)在xml配置文件中添加拦截器,目前只支持xml方式来配置拦截器,这个还得需要mybatis-spring后续版本做支持,注意这里是有顺序要求的,plugins标签要放到mappers标签之前

5)最后我们执行以下程序看是否输出对应的结果
mybatis之插件机制
文章图片

mybatis之插件机制
文章图片

可以看到拦截住了并且输出了我们期望的结果。
插件实现原理
插件机制如此灵活,那mybatis是如何实现的呢?下面我们来揭开mybatis插件机制的神秘面纱。首先我们来看一张图
mybatis之插件机制
文章图片

这里就是所有插件相关的代码逻辑,我们要实现的拦截器接口Interceptor
mybatis之插件机制
文章图片

intercept 方法就是我们自己定义具体嵌入逻辑的点。所有插件都是通过InterceptorChain来进行统一注册管理的
mybatis之插件机制
文章图片

interceptors 管理了所有的插件,pluginAll 方法对对象进行插件封装。InterceptsSignature就用来自定义需要拦截的类的方法。核心逻辑都在Plugin
mybatis之插件机制
文章图片

可以看到这里主要是基于jdk的动态代理做的。结合例子梳理一下整个流程
1)我们在打开session的时候会创建一个executor,在创建的时候,mybatis会给executor加一层插件代理
mybatis之插件机制
文章图片

2)在我们自己实现的拦截器的 plugin 函数中,我们调用了 Plugin.wrap进行了一层封装
mybatis之插件机制
文章图片

它会对我们的类签名进行解析,来确认具体要代理的类的方法有哪些,解析逻辑在org.apache.ibatis.plugin.Plugin#getSignatureMap,在我们的例子中,是对 Executor 的 query 方法进行了拦截
mybatis之插件机制
文章图片

3)我们在执行查询逻辑的时候,就由代理走到了拦截方法中
mybatis之插件机制
文章图片

总结
【mybatis之插件机制】mybatis 的插件机制整体还是非常简单的,我们自己开发的时候也可以参照实现插件。

    推荐阅读