前言
Mybatis 可以算是大家日常工作中最常碰到的框架工具了,有了 Mybatis 我们在对数据库操作时不用再像jdbc那样还需要关注如何连接数据,数据获取后连接如何处理等一系列和数据无关的操作。所以笔者这里也算是更加理解了" 一个优秀的框架能够使开发者更加关注于自身的业务逻辑上"这句话的含义。
使得 Mybatis 优秀的不仅仅是他的功能上,还体现在他的扩展性上。 Mybatis 提供了一种插件的功能,可以使得 Mybatis
- 添加在
ParameterHandler
,使得在参数解析后,组成sql前可以对参数进行修改 - 添加在
StatementHandler
,使得在sql准备执行前,可以修改sql语句 - 添加在
ResultSetHandler
,可以对结果返回进行修改加强 - 添加在
Executor
,可以作用在请求的全过程中
这篇文章主要想讨论一下 Mybatis 插件的实现机制,以及他使用了什么思想。
动态代理的运用
Mybatis 插件的底层实现原理也是使用了动态代理,这里先简单介绍一下动态代理。动态代理在java 1.3中就已经实现了,
这里可能有同学会问,直接在玩王者的方法中加上吃饭,不就可以了,为啥需要这么麻烦?在只有一个函数需要进行这样处理时确实可以这样做,但是如果有非常多的函数都需要这样处理的话,我们就需要再每一个函数中都添加上这样的逻辑, 这样代码非常冗余,而且逻辑需要修改的时候也需要全部进行修改,不好维护。 在这种情况下我们就可以使用这种方法来解决在每一个需要动态加强的函数上使用动态代理,其实这也是一种面向切面的编程方式。
动态代理使用
动态代理实现上非常简单,涉及到的类也不多,第一需要介绍的就是Proxy
这个类。需要使用动态代理去增强某个函数的时候,是需要有一个中介的,需要去沟通是那一个接口函数需要进行增强,具体增强的函数是哪个,需要对他们牵个手,认识一下。Proxy
类中newProxyInstance
函数就是做了这样的工作。
看到了不,这个方法中就是传入了接口和具体的实现方法。知道了这个后,我们只需要再去看下InvocationHandler
这个类。
看了一下这个其实就一个接口,所以我们在使用时只需要实现这个接口,然后具体实现invoke
这个函数就可以了, 这样最后产生的结果就是我们在调用特定接口下的函数时,不会直接调用接口下的具体实现,而是会调用到这个invoke
函数,我们可以再这个函数中对原来的实现经加强。
限于篇幅,这里就不在详细讲动态代理的实现了,具体有兴趣的同学可以去戳这篇文章,写的很好。
Mybatis中的插件
首先看下官网上对于插件的例子,需要实现Interceptor
接口,并且在上面使用注解@Intercepts
和@Signature
,其中@Intercepts
是放在外层用来标识一个@Signature
注解的集合,所以@Signature
可以有多个。@Signature
的值分别为所需要代理的类(也就是Executoer.class,ParameterHandler.class等这四种),代理的方法(官网上有写到的类旁边对应的方法),和代理的方法中传入的参数。
先总结下,在 Mybatis 中我们可以对四个位置实现加强(为啥只能加强这四种类型呢? 因为所有数据的操作,这四种都能够涉及到了,也不需要其他地方进行加强了,Mybatis 功能很纯粹的)。我们可以对一个位置做出多个插件,例如对于ResultSetHandler
我们可以写一个插件用来做数据脱敏,同时也可以再写一个插件用来过滤到我不想要的数据等等。
实现逻辑
大致流程图上已经画出来了,以查询功能为例,我们在执行查询时,会去获取Configuration
类对象,组件一个StatementHandler
对象,StatementHandler
用来组成Statement
对象,Statement
对象上已经包含了所有的参数,就可以直接去查询了。类型特别多是不是,其实可以理解为我们要执行sql查询功能就需要StatementHandler
对象,这个对象能帮我们搞定一切。
既然这个对象这么牛逼,那么我们需要加强的功能相必也是作用在这个上面了。我们对上面图中的流程简单的就不一一分析了,主要分析一下其中的重点:pluginAll()函数到底做了些什么?
是不是非常简单,获取到之前设置进去的interceptors
列表,然后依次遍历其中的plugin()
函数。那么要弄懂其中的奥秘,就需要我们进入到Interctptor
这个接口中去了。
好家伙,原来具体又委托给了Plugin
类,真是一群承包商。哈哈,大家注意咯,Plugin
类就是最终实现的地方了。一起进入到Plugin#warp
函数
具体产生的效果就如下,会一层一层的添加上加强的函数。而具体调用的地方是如何实现的呢?
而Interceptor.intercept
函数就是我们实现Interceptor
接口的地方了,下面举一个栗子
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class EncryptInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 对参数进行处理操作
if (!(invocation.getTarget() instanceof ParameterHandler)) {
return invocation.proceed();
}
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
Object parameterObject = parameterHandler.getParameterObject();
if (parameterObject != null) {
// 具体操作参数,利用反射的功能
}
return invocation.proceed();
}
....
}
这就是 Mybatis 插件的实现原理了,不知道写的是否清楚,欢迎大家留言讨论!!!