
面试官:说一下Mybatis插件的实现原理?
介绍
我之前有篇文章大概写了一下mybatis插件的实现原理
Mybatis框架和插件将动态代理玩出了新境界
Mybaits插件的实现主要用了责任链模式和动态代理
动态代理可以对SQL语句执行过程中的某一点进行拦截,当配置多个插件时,责任链模式可以进行多次拦截,责任链模式的UML图如下
可以看到在一条责任链中,每个Handler对象都包含对下一个Handler对象的引用,一个Handler对象处理完消息会把请求传给下一个Handler对象继续处理,以此类推,直至整条责任链结束。这时我们可以改变Handler的执行顺序,增加或者删除Handler,符合开闭原则
写一个Mybatis插件
mybatis可以拦截如下方法的调用
- Executor(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)
- ParameterHandler(getParameterObject,setParameters)
- ResultSetHandler(handleResultSets,handleOutputParameters)
- StatementHandler(prepare,parameterize,batch,update,query)
至于为什么是这些对象,后面会提到
写一个打印SQL执行时间的插件
在mybatis配置文件中配置插件
此时就可以打印出执行的SQL和耗费的时间,效果如下
原理分析
前面说过Mybatis是通过动态代理的方式来额外增加功能的,因此调用目标对象的方法后走的是代理对象的方法而不是原方法
说到这你可以能意识到拦截器只需要实现InvocationHandler接口就行了,先对指定对象生成一个代理类,然后在InvocationHandler的invoke方法中对指定方法进行增强。
但继承InvocationHandler后,生成代理类,并对指定方法进行增强这不是个累活么,框架完全可以再帮你封装一下啊
于是就有了@Intercepts注解,里面主要放多个@Signature注解,而@Signature注解则定义了要拦截的类和方法、
并且提供了Interceptor接口和Plugin类方便你实现动态代理,来看看他们怎么配合使用的吧
我们先从Interceptor接口来分析,因为要插件必须要实现Interceptor接口
其中plugin方法就是生成代理对象的,一般的做法是直接调用Plugin.wrap(target, this);方法来生成代理对象,到Plugin类里面看看,主要的方法如下
到现在为止,实现代理类和拦截特定方法用一个Plugin.wrap()方法就搞定了,贼方便。
在Plugin.invoke()方法中,最终调用了Interceptor接口的intercept方法,并把目标类,目标方法,参数封装成一个Invocation对象
接着看Invocation的定义
只有一个方法proceed()方法,而proceed()只是执行被拦截的方法,这时清楚了应该在Interceptor对象的intercept方法中做哪些操作了,只需要写增强的逻辑,最后调用Invocation对象的proceed()方法即可
至此我们已经大概理解了插件的工作原理,只差最后一步了,给目标对象生成代理对象,我们从Mybatis初始化找答案
在配置文件中配置插件的格式如下,interceptor填全类名,下面可以写多个key和value值,实现了Interceptor接口后,会有一个setProperties方法,会把这些属性值封装成一个Properties对象,设置进来
mybatis配置文件的解析在XMLConfigBuilder的parseConfiguration方法中,这里我们只看一下插件的解析过程
实例化好的Interceptor对象,会被放到InterceptorChain对象的interceptors属性中
InterceptorChain对象的pluginAll方法不就是用来生成代理对象的吗?看看在哪调用了
这不就是前面提到的mybatis只能拦截特定类的原因吗?因为只对这些类做了代理。
至此Mybatis插件的原理就分析完了,还是挺简单的。但是要写一个实用的Mybatis插件并不容易,因为你要明白ParameterHandler等类的方法做了哪些事情,应该如何进行增强
最后总结一下,@Signature注解主要用来定义要拦截的类及其方法,而Interceptor接口和Plugin来配合为指定对象生成代理对象,并拦截指定方法,这样就能在其前后做一些额外操作
文章转载自公众号:Java识堂
