最近想研究一下mybatis如何注册插件的,发现Mybatis的拦截器是用代理实现的,写法再一次被惊艳到,下面是我的一些debug笔记和思考笔记笔记
mybatis通过默认工厂创建sqlsession的时候,会去创建一个Executor的代理类
通过一个select方法观察到。mybatis在执行selectList方法的时候, 产生了如下的调用栈
在执行这个方法newExecutor的时候,会根据ExecutorType去new自己写好的Executor实现,然后会去调用executor = (Executor) interceptorChain.pluginAll(executor);这个关键方法注册所有的拦截器
点进去pluginAll(executor)这个方法
会发现,InterceptorChain遍历所有拦截器都去调用它的plugin方法(父接口有默认的plugin实现),再次点进去plugin方法。发现他调用了Plugin.wrap方法。
在这个方法的第一行出现了getSignatureMap方法,debug后发现这个方法处理了自己写的interceptor类的注解里面包含的class和目标方法(getSignatureMap通过注解里面的类名和方法参数找到了具体应该拦截到的方法(Method method =sig.type().getMethod(sig.method(), sig.args());),如图这个方法结束后把所有参数都放到map集合中去
到这里,可以得出回到wrap方法。这个方法是一个把所有的拦截器一一组成一个的代理链的方法,他通过代理最初的自己写好的Executor接口的类,如果拦截器注解标注的接口,和要被代理的类如果有交集的话,接回就会继续生成代理并返回一个代理类,如果不是的话就返回自己target本身,这样一个链条式的代理类就生成了。起初没看明白动态的链式拦截器是怎么生成的,其比较关键的就是在Plugin类的wrap方法上,不断把自己作为成员属性传入Plugin,生成新的代理对象。然后在执行代理方法的时候。将自身的target传入invoke方法,因为target是下一个代理对象,所以就又会继续执行它的method,被invoke方法继续往下代理,然后又是相同的流程,直到最后的目标方法被执行,确实比较难理解,但总之就是套娃。。。。。。
再看一个设计的精妙的点,当代理拦截到想要的方法时,会去调用拦截器的intercept方法,并把被代理的方法,代理链的下一个代理对象(注意是下一个代理对象,不是当前代理对象)和method需要的参数方法封装成了Invocation传给拦截器接受,拦截器在这里可以执行自己自定义的方法后决定调用链要不要继续继续往下执行。(调用Invocation的proceed方法就可以往下执行了)
还有一个比较重要的点,就是自己在书写拦截器的时候,拦截的Exectur接口的query方法有两个,拦截器的执行顺序时和自己的注册顺序是相反的,比如说有拦截器先后注册了1,2,3,那么执行顺序是3,2,1,query,1,2,3,因为先注册的在interceptChain的pluginAll方法里面遍历的顺序最先。
在Mybatis自己写好的Exectur实现类里面,四个参数的query方法最终调用了6个参数的query方法。也就是说,如果某个拦截四个参数的query方法的时候,又去调用了6个参数的query方法,就会导致在这个拦截器后面的拦截器拦截不到四个参数的方法,导致拦截器失效。
所以这样的话,为了使所有的拦截器都生效,就把所有拦截四个参数的拦截器放在6个参数的query方法前面,拦截6个参数的拦截器放在6个参数的query后面就可以了