mybatis插件的原理
前面我们知道拦截器怎么写了,接下来简单的分析一波原理。
前几天我看到一个观点是说看开源框架的源码建议从 mybatis 看起。我是很赞成这个观点的,确实是优雅,而容易看懂。能品出很多设计模式的使用。
一句话总结 mybatis插件的原理就是:动态代理加上责任链。
先看一下 Plugin 类的动态代理:
标号为 ① 的地方一看就知道,InvocationHandler,JDK 动态代理,没啥说的。
标号为 ② 的地方是 wrap 方法,生成 Plugin 代理对象。
标号为 ③ 的地方是 invoker 方法,圈起来的目的是想说是在这里判断当前方法是否是需要被拦截的方法。如果是则用代理对象走拦截器逻辑,如果不是则用目标对象,走正常逻辑。
给大家看一下这个地方的 debug 效果:
一个平平无奇的 if 判断,是拦截器的关键。为什么这个地方多说了几句呢?
因为其实这就是细节的地方。当面试的时候面试官问你:mybatis 是怎么判断是否需要拦截这个方法的时候你能答上来。说明你是真的看过源码。
责任链是怎么体现的呢?
就是这个地方: org.apache.ibatis.plugin.InterceptorChain
你看又学到一招,mybatis 里面的设计模式还有责任链。
我们看一下 pluginAll 方法的调用方:
这个地方就体现出之前官网说的了:
插件是作用于这四大对象的:Executor、ParameterHandler 、ResultSetHandler 、StatementHandler 。
上面框起来的这四个框,就是插件调用的地方。
那么插件在什么时候被加载,或者说什么是被注册上的呢?
还是回到拦截链这个类上去:
pluginAll 方法我们已经知道有哪些地方调用了。这个方法里面其实还有两个考点。
第一就是 interceptor 这个 List 集合的定义,用了 final 修饰。所以要注意 final 修饰基本类型和引用类型的区别,被 final 修饰的引用类型变量内部的内容是可以发生变化的。
第二就是 getInterceptors 返回的是一个不可修改的 List 。所以,要对集合 interceptors 进行修改,只能通过 addInterceptor 方法进行元素添加,保证了这个集合是可控的。
所以,我们只需要知道哪里调用了 addInterceptor 方法,哪里就是插件被注册的地方。
一个是 SqlSessionFactoryBean ,一个是 XMLConfigBuilder。
使用 XML 配置是这样的:
熟悉 mybatis 的朋友们肯定知道,无非就是对于标签的解析而已。
解析到 plugins 标签,则进入 pluginElement 方法中,在这个方法里面调用 addInterceptor:
本文没有使用 XML 的形式配置,所以我们主要看一下 SqlSessionFactoryBean。
怎么看呢?
不要盲目的走入源码,加个断点看调用链,跟着调用链去走就很清晰了。
在这个地方加一个断点:
然后 debug 起来,你就可以看到整个调用链了:
然后我们根据上面的调用链,我们就可以找到源头了:
在 MybatisAutoConfiguration 的构造方法里面初始化了 interceptors。
而 interceptorsProvider.getIfAvailable() 方法也解释了为什么我们只需要在程序里面这样注入我们的拦截器就可以被找到了:
对 getIfAvailable 方法不熟悉的朋友可以去补一下这块的知识,我这里只是给大家看一下这个方法上的注释:
当然,你这样去注入的话有可能会不生效,你就会大骂一声:写的什么垃圾玩意,配置上了也不对呀。
别着急呀,我还没说完呢。你看看是不是有自定义的 SqlSessionFactory 在项目里。
看一下注入 SqlSessionFactory 的源码上面的那个注解了吗?
@ConditionalOnMissingBean ,看名字也知道了,当你的项目里面没有自定义的 SqlSessionFactory 的时候,才会由源码给你注入,这个时候才会正在的注册上插件:
如果你有自定义的 SqlSessionFactory,那么请手动调用 factory.setPlugins 方法。
所以,总结一下插件的三种配置方法:
1.xml方式配置。
2.如果没有自定义 SqlSessionFactory 直接 @Bean 注入拦截器即可。
3.如果有自定义 SqlSessionFactory 需要在自定义的地方手动调用 factory.setPlugins 方法。
其实我尝试过第四种方法,在application.properties 里面配置:
这种配置方式才是符合 SpringBoot 思想的配置。才是真正的丝滑,润物无声的丝滑。
可惜,我配置上后,点击到对应的源码地方一看:
它调用的是 getInterceptors 方法,我就知道肯定是有问题了:
果然,运行起来会报这样的错误: Failed to bind properties under 'mybatis.configuration.interceptors' to java.util.List<org.apache.ibatis.plugin.Interceptor>
找了一圈原因,最后发现了这个 issue:
github.com/mybatis/spring-boot-starter/issues/180
这个“奇异博士”头像的用户提出了和我一样的问题:
然后下面的回答是这样的:
别问,问就是不支持。请使用 @Bean 的方式。
最后说一句(求关注)
点个“赞”吧,周更很累的,不要白嫖我,需要一点正反馈。
才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你指出来,我对其加以修改。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。
我是 why,一个被代码耽误的文学创作者,不是大佬,但是喜欢分享,是一个又暖又有料的四川好男人。
欢迎关注我的微信公众号:why技术。在这里我会分享一些java技术相关的知识,用匠心敲代码,对每一行代码负责。偶尔也会荒腔走板的聊一聊生活,写一写书评、影评。感谢你的关注,愿你我共同进步。