Mybatis插件机制原理

在Mybatis中,有一个强大的机制可以让我们快速地侵入到Mybatis的底层操作,从而去扩展Mybatis的功能,这就是插件机制,我们也可以叫作Mybatis的拦截器机制。

一.基本原理

在Mybatis架构体系中,有着四大组件(对象),分别是Executor,StatementHandler,ParameterHandler,ResultSetHandler。而我们在阅读Mybatis的底层源码的时候,可以很容易地发现在Mybatis中对这四大组件进行创建的时候都调用了一个interceptor.plugin的方法。所以说我们的插件针对的只能是这四大对象。顾名思义,interceptor中文就是拦截器的意思,说到拦截器我们很容易地就想到了它的功能就是在某个方法在被执行之前或被执行之后去增强这个方法的逻辑,这里其实就有点AOP(切面编程)的味道了。而对于Mybatis的插件机制来说,它的实现正是使用的jdk的动态代理机制实现的切面编程,为我们的四大对象都生成一个代理对象,从而去增强我们四大对象执行对应方法的逻辑。

二.插件基本使用

在这里简略地讲下我们的插件是怎么使用的。

运行结果:

这样这个Intercepor就配好了,并且在Executor执行query方法的时候会执行里面的intercept方法的逻辑。

三.插件底层原理的实现

为什么我们像上图这样配置就能够使我们的插件生成四大组件的代理对象从而能执行到里面的intercept方法的增强逻辑尼?而且可以发现打印的日志“拦截的目标类”都是打印出四大对象,下面我们一起来看源码来了解下。

首先我们这里随便看一下四大组件其中一个组件在创建后插件对其做了什么操作,这里就以Executor为例吧,进入到初始化Executor的方法。

Executor初始化的逻辑是在创建SqlSession的过程中创建的,所以我们来到创建SqlSession时里面的某一段源码中

对于Mybatis中SqlSession创建过程的解析,可以回去看:

Mybatis原理解析(二)SqlSession的创建过程

这里有执行了一句interceptorChain.pluginAll(executor)方法,仔细看这个方法,我们在创建出Executor对象的时候,把这个对象传进这个方法,然后就又返回了Executor对象给我们了,猜测里面应该是对这个对象进行了包装,我们可以再点进去这个方法看看

里面是拿到每一个Interceptor然后调用了interceptor的plugin方法,这个方法把我们的Executor对象拿到之后执行完就又返回给我们了,所以说这个方法就是对我们的四大对象进行包装的方法了,而前面我们也说过了,插件机制的实现核心在于对目标对象生成动态代理,所以说我们在实现自己的interceptor的时候,plugin方法里面我们就是要拿到目标类,对目标类生成动态代理对象然后返回了。

此时我们的目标很明确了,就是要在自定义的Interceptor的plugin方法里面对目标类生成动态代理并且返回,那么我们现在回到我们自定义的Interceptor的plugin方法中

可以看到里面应该是创建目标类的动态代理对象的,而这里直接调用了一个Plugin.wrap(target,this)方法,传入了目标类以及自身对象,那么我们进入这个方法看看,而直接看源码的话不太好理解,所以我们现在直接debug看看

在这里我们发现有一个getSignatureMap,首先我们从名字就感觉到有点熟悉对不对?signature这个单词好像哪里有写过,对,其实我们就在给我们的自定义Interceptor这个类上面写过这个注解,而这个方法会不会就是拿到我们在Interceptor中写的所有的@Signature注解的信息尼?我们深入看看这个方法

 可以发现原来这个方法和我们的猜测一致,就是去解析我们的写的注解的信息的。接着我们再往下去看

发现我们拿到了目标类(这里的目标类就是四大对象)的类对象,然后把类对象和上面返回的map传入了一个getAllInterfaces方法中,看名字应该是拿接口类对象的,我们继续深入

 最终返回给我们的就是包含有四大对象的类对象的数组了,

而如果这个数组的长度不大于0的话,那么就说明这个目标对象不需要被拦截,直接返回这个目标对象,而如果大于0的话,说明这个目标对象需要被拦截处理,则用jdk的动态代理机制生成对应的代理对象。这样整个wrap方法就执行成功了。那么这里就总结下wrap方法:首先这个方法会解析我们在自定义Interceptor类上面的所有注解信息,以type属性值为key,以根据method,args属性值生成对应的type的Method对象组成的set集合为value,然后再去根据这个map去配对有没有以四大组件类对象为key的entry,如果有,就把目标对象实现的接口类对象(即四大组件之一)放到数组中,最后判断这个数组大小是否大于0,大于0的话说明此刻需要为目标对象创建一个动态代理对象,否则就直接返回这个目标对象即可。

在得到了目标对象的代理对象之后,之后执行的这个目标对象的所有方法都会先去执行代理对象的invoke方法了。而我们上面传入的实现了InvocationHandler接口的类就是Plugin类,我们进入这个类的invoke方法看看

可能看到这里你会有个疑问,就是如果当前执行的方法是我们指定要拦截的方法,然后就执行了return interceptor.intercept(new Invocation(target,method,args)),可以看到这是返回了自定义拦截器的intercept方法的返回值,那么我们知道动态代理中要对一个方法进行增强,那么增强之后还要去执行原来的代理的,即一定要返回method.invoke(target,args)的返回值才会执行原来的代码,那么我们上面最终直接返回intercept方法的返回值不就在增强之后执行不了原始方法的逻辑了吗?其实不然,我们的intercept方法最后返回的其实就是method.invoke(target,args)这句代码的返回值,因为我们在intercept方法中最终调用了invocation.proceed()这句代码,而这句代码里面,我们可以看看

其实这句代码里面还是method.invoke(target,args)这句代码,所以说我们在增强了方法之后还是会执行原始方法的逻辑的。 

最后总结Mybatis的插件机制的原理:插件其实就是只针对四大对象有作用的,因为在Mybatis里面写死了在四大对象创建的过程中,都会把对象扔到插件当中去,从而生成对应的动态代理对象。而生成动态代理对象的方法也正是在插件的plugin方法中的去实现的,Mybatis对生成动态代理对象封装了一个Plugins类的wrap方法,这个方法原理是首先去拿到我们在自定义的Interceptor类上面的@Intercepts以及@Signature注解的信息去封装成一个以type属性值为key,以根据method,args属性值生成对应的type的Method对象组成的set集合为value的map对象,然后这个map里面尼就缓存了哪个类的哪个方法要被拦截增强处理,而在执行被代理对象的方法的时候就会进入到invoke方法中去,在invoke方法中去判断当前执行的方法是否是有在map中存在,如果存在就执行增强方法(intercept方法),如果不存在就执行原始方法的逻辑。所以插件的核心就是动态代理机制!

猜你喜欢

转载自blog.csdn.net/weixin_37689658/article/details/99686884