【Mybatis源码阅读】从源码角度分析插件工作原理

551 阅读2分钟

MyBatis通过提供插件机制,让我们可以根据自己的需要去增强MyBatis的功能。从而实现了在不修改原有代码的前提下,改变四大核心对象的行为。本文将会从源码角度分析插件的工作原理。

源码地址:

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)

下面我们以Executor为例,进行详细介绍。

Executor织入拦截器

Executor拦截器在openSession()时织入。具体来说是在创建Executor进行织入。 image.png image.png

从源码中可以看出,创建完Executor之后,就会调用InterceptorChainpluginAll()方法织入拦截器。 image.png

InterceptorChain对象就是所有插件的集合,pluginAll()就是遍历插件,并执行插件的plguin()方法。插件接口定义如下: image.png

其中plugin()方法的实现一般都是Plugin.wrap()image.png

继续看Plugin.wrap()源码实现: image.png

wrap()通过JDK代理,返回一个代理对象,该代理的InvocationHandler就是Plugin对象。因此执行的时候最终会执行Plugininvoke()方法。这个我们之后再看。

如果没有匹配上需要代理的接口,则不会进行代理

StatementHandlerparameterHandlerresultSetHandler对象中织入拦截器的方式跟Executor一样,都是在创建完对象之后,调用调用InterceptorChainpluginAll()方法织入拦截器。不在赘述

插件调用流程

我们知道调用Mapper接口的方法,最后都会执行Executorupdate()或者query()方法。而在有插件的情况下,Executor是一个代理对象,因此会先调用Plugininvoke()方法: image.png

Invocation定义如下: image.png

在执行完成插件逻辑之后,都会调用Invocationproceed()方法。

从源码可以明显看出,Mybatis的插件是通过层层包装来实现拦截器链式调用的。假设有2个Executor插件,当我们调用Executorquery()时,第一次会调用Plugininvoke()方法。此时的taget仍是一个代理对象,因此在第一个插件调用的最后执行invocation.proceed()方法会再次调用Plugininvoke()方法织入下一个插件的逻辑,而此时target就是实际的Executor对象,最后会调用原有方法实现执行后续操作。

这里跟Spring的AOP实现思路不同,Spring的AOP是只包装一层,在调用的时候先在那一层代理里面遍历所有织入的逻辑。最后再执行原有方法。

原创不易,觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~

欢迎关注我的开源项目:一款适用于SpringBoot的轻量级HTTP调用框架