MyBatis插件
一、插件简介
⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯作。
以MyBatis为例,我们可基于MyBatis插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能
二、mybatis插件介绍
Mybatis作为⼀个应⽤⼴泛的优秀的ORM开源框架,这个框架具有强⼤的灵活性,在四⼤组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易⽤的插件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的动
态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象
MyBatis所允许拦截的⽅法如下:
- 执⾏器Executor (update、query、commit、rollback等⽅法);
- SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
- 参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
- 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);
三、mybatis插件原理
在四⼤对象创建的时候
- 每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
- 获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返回 target 包装后的对象
- 插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏
- interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调⽤拦截器链中的拦截器依次的对⽬标进⾏拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四⼤对象。返回的target是被重重代理后的对象
在mybatis中configuration中,四大对象创建时都会被放入拦截器链中,返回代理对象。
四、自定义mybatis插件
插件接口:
- 通过注解
@Intercepts
和@Signature
确定拦截的类、方法。 - 实现
Interceptor
接口,将插件设置为拦截器 - 实现
Interceptor
中的方法。 - 在mybatis配置文件中配置插件properties属性
代码实现:
package zlt.example.plugin;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Properties;
/**
* 自定义mybatis插件类
*
*/
@Intercepts({//设置拦截器,可以设置对多个方法进行处理
@Signature(
type = StatementHandler.class,// 拦截哪个接口
method = "prepare",// 要拦截的方法
args = {Connection.class, Integer.class}// 拦截方法的入参,由于可能出现重载方法的情况,所以参数要准备以确定对哪个方法进行拦截
)
})
public class MyPlugin implements Interceptor {
/**
* 每次执行操作的时候,都会进行这个拦截器的方法内
*
* @param invocation 代理类中的执行
* @return 原方法执行
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("这里增强了逻辑");
System.out.println("获取invocation中的参数getArgs:" + Arrays.toString(invocation.getArgs()));
System.out.println("获取invocation中的参数getTarget:" + invocation.getTarget());
return invocation.proceed();
}
/**
* 把当前这个拦截器生成一个代理放到拦截器链中
*
* @param target 为要拦截的对象
* @return 代理对象
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
// return Interceptor.super.plugin(target);
}
/**
* 获取配置文件中设置的属性,插件初始化的诶时候调用,也只调用一次,插件配置的属性从配置中获取
*
* @param properties 置文件中设置的属性
*/
@Override
public void setProperties(Properties properties) {
System.out.println("获取对配置文件中设置的属性properties:" + properties);
Interceptor.super.setProperties(properties);
}
}
配置完插件后,mybatis在启动时就可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中。
待准备⼯作做完后,MyBatis处于就绪状态。我们在执⾏SQL时,需要先通过DefaultSqlSessionFactory创建 SqlSession。StatementHandler 实例会在创建 SqlSession 的过程中被创建, StatementHandler实例创建完毕后,MyBatis会通过JDK动态代理为实例⽣成代理类。这样,插件逻辑即可在 Executor相关⽅法被调⽤前执⾏。
五、源码分析
Plugin实现了 InvocationHandler接⼝,因此它的invoke⽅法会拦截所有的⽅法调⽤。invoke⽅法会对所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取被拦截的方法列表,例如:signatureMap.get(Executor.class), 可能返回 [query, update,commit],如果当前执行的方法包含在拦截的方法列表中,那么进入判断
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 检测方法列表如果包含被拦截的方法,那么执行插件逻辑.
return interceptor.intercept(new Invocation(target, method, args));
}
// 执行被拦截的方法。
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
⾸先,invoke⽅法会检测被拦截⽅法是否配置在插件的@Signature注解中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法。插件逻辑封装在intercept中,该⽅法的参数类型为Invocationo, Invocation主要⽤于存储⽬标类,⽅法以及⽅法参数列表。
六、pageHelper分页插件
MyBatis可以使⽤第三⽅的插件来对功能进⾏扩展,分⻚助⼿PageHelper是将分⻚的复杂操作进⾏封装,使⽤简单的⽅式即可获得分⻚的相关数据
1. 导入依赖
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>