MyBatis:MyBatis插件
2023-04-03 本文已影响0人
alex很累
一、简介
MyBatis中的插件也可以称之为拦截器,开发者可以通过插件在MyBatis某个行为执行时进行拦截并改变这个行为。这样的好处显而易见,一是增加了框架的灵活性,二是开发者可以根据自己的实际情况,对MyBatis进行增强;例如,我们可以基于MyBatis的插件机制实现分页、分表、监控等功能。
二、MyBatis插件介绍
MyBatis在如下接口处提供了插件扩展机制:
可作用接口 | 接口说明 | 可作用方法 |
---|---|---|
Executor | 执行器 | update(),query(),flushStatements(),commit(),rollback(),getTransaction(),close(),isClosed() |
StatementHandler | SQL语法构建器 | prepare(),parameterize(),batch(),update(),query() |
ParameterHandler | 参数处理器 | getParameterObject(),setParameters() |
ResultSetHandler | 结果集处理器 | handleResultSets(),handleOutputParameters() |
MyBatis中的插件可以作用于这四大对象进行拦截,来增强核心对象的功能;增强的本质是借助于底层的动态代理来实现的。
三、插件的使用
1. 修改MyBatis配置文件
<plugins>
<plugin interceptor="cn.alex.example.test.MyPlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
2. 实现Interceptor接口,指定拦截的方法
@Intercepts({
// 可以写多个@Signature,表示对多个地方进行拦截
@Signature(type = Executor.class, // 作用的接口
method = "query", // 拦截的方法
args = {MappedStatement.class, Object.class, RowBounds.class}) // 拦截方法的入参
})
public class MyPlugin implements Interceptor {
private Properties properties = new Properties();
/**
* 增强逻辑
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取被拦截的对象
Object target = invocation.getTarget();
// 获取被拦截的方法
Method method = invocation.getMethod();
// 获取被拦截的方法的参数
Object[] args = invocation.getArgs();
// 在方法执行前执行一些事情
System.out.println("对方法执行前进行了增强...");
// 执行原来的方法
Object result = invocation.proceed();
// 在方法执行后执行一些事情
System.out.println("对方法执行后进行了增强...");
return result;
}
/**
* 为了把这个拦截器生成一个代理放到拦截器链中
* @Description 包装目标对象,为目标对象创建代理对象
* @param target 目标对象
* @return 代理对象
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 获取配置文件中的属性
* @Description 插件初始化的时候调用(只会执行一次),插件配置的属性从这里设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
四、插件原理简析
MyBatis在启动时会加载插件,把配置的插件解析成拦截器Interceptor
并保存到相关对象InterceptorChain
(拦截链)中。
而MyBatis的四大对象在创建时,比如说Executor
,不是直接返回得出的,而是由interceptorChain.pluginAll(executor)
得到的;所以插件逻辑的植入,也就发生在这里。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
在pluginAll
方法中,会遍历每个插件并执行plugin
方法;也就是说,插件功能的添加在于Interceptor
的plugin
方法。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
而Interceptor
的plugin
方法,其实是调用了Plugin.wrap(target, this)
;在swap
方法中,先是判断了插件拦截的目标是否包含了目标对象,如果有的话,为目标对象基于JDK动态代理生成代理对象。
public static Object wrap(Object target, Interceptor interceptor) {
// 将插件的@Signature注解内容获取出来并生成映射结构
// Map[插件作用的接口的Class对象, Set[插件作用的方法的方法对象]]
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 将目标对象实现的所有接口中是当前插件的作用目标的接口获取出来
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 为目标对象生成代理对象并返回
// 这是JDK动态代理的应用
// Plugin实现了InvocationHandler接口
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
而代理对象在执行方法的时候,其实是在调用Plugin
的invoke
方法,在这个方法中,会判断当前插件是否作用于当前对象执行的方法上,如果不作用,则直接执行方法;如果作用,则执行插件的逻辑。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断插件是否作用于当前代理对象执行的方法
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);
}
}