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进行织入。
从源码中可以看出,创建完Executor之后,就会调用InterceptorChain的pluginAll()方法织入拦截器。
InterceptorChain对象就是所有插件的集合,pluginAll()就是遍历插件,并执行插件的plguin()方法。插件接口定义如下:
其中plugin()方法的实现一般都是Plugin.wrap():
继续看Plugin.wrap()源码实现:
wrap()通过JDK代理,返回一个代理对象,该代理的InvocationHandler就是Plugin对象。因此执行的时候最终会执行Plugin的invoke()方法。这个我们之后再看。
如果没有匹配上需要代理的接口,则不会进行代理
在
StatementHandler、parameterHandler和resultSetHandler对象中织入拦截器的方式跟Executor一样,都是在创建完对象之后,调用调用InterceptorChain的pluginAll()方法织入拦截器。不在赘述
插件调用流程
我们知道调用Mapper接口的方法,最后都会执行Executor的update()或者query()方法。而在有插件的情况下,Executor是一个代理对象,因此会先调用Plugin的invoke()方法:
Invocation定义如下:
在执行完成插件逻辑之后,都会调用Invocation的proceed()方法。
从源码可以明显看出,Mybatis的插件是通过层层包装来实现拦截器链式调用的。假设有2个Executor插件,当我们调用Executor的query()时,第一次会调用Plugin的invoke()方法。此时的taget仍是一个代理对象,因此在第一个插件调用的最后执行invocation.proceed()方法会再次调用Plugin的invoke()方法织入下一个插件的逻辑,而此时target就是实际的Executor对象,最后会调用原有方法实现执行后续操作。
这里跟
Spring的AOP实现思路不同,Spring的AOP是只包装一层,在调用的时候先在那一层代理里面遍历所有织入的逻辑。最后再执行原有方法。
原创不易,觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~
欢迎关注我的开源项目:一款适用于SpringBoot的轻量级HTTP调用框架