Mybatis源码解读-插件
2024-10-20 08:02:21
插件允许对Mybatis的四大对象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)进行拦截
问题
Mybatis插件的注册顺序与调用顺序的关系?
使用
在讲源码之前,先看看如何自定义插件。
创建插件类
自定义插件类需要实现Interceptor
// 注解配置需要拦截的类以及方法
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
})
// 实现Interceptor接口
public class SqlLogPlugin implements Interceptor { /**
* 具体的拦截逻辑
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long time = System.currentTimeMillis() - begin;
System.out.println("sql 运行了 :" + time + " ms");
}
} /**
* 判断是否需要进行代理
* 此方法有默认实现,一般无需重写
*/
/*@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}*/ /**
* 自定义参数
*/
@Override
public void setProperties(Properties properties) {
// 这是xml中配置的参数
properties.forEach((k, v) -> {
System.out.printf("SqlLogPlugin---key:%s, value:%s%n", k, v);
});
}
}
注册
在配置文件注册插件
<plugins>
<plugin interceptor="com.wjw.project.intercaptor.SqlLogPlugin">
<property name="key1" value="root"/>
<property name="key2" value="123456"/>
</plugin>
</plugins>
效果
控制输出
SqlLogPlugin---key:key1, value:root
SqlLogPlugin---key:key2, value:123456
sql 运行了 :17 ms
源码
原理:Mybatis四大对象创建时,都回去判断是否满足插件的拦截条件,满足,则四大对象就会被Plugin
类代理
源码分3部分讲。注册、包装、调用
注册
xml方式的注册,是在XMLConfigBuilder#pluginElement完成的。
不明觉厉的同学,请参考上一篇文章:Mybatis源码解读-配置加载和Mapper的生成
// XMLConfigBuilder#pluginElement(XNode parent)
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 读取插件的类路径
String interceptor = child.getStringAttribute("interceptor");
// 读取自定义参数
Properties properties = child.getChildrenAsProperties();
// 反射实例化插件
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
// 将插件添加到配置的插件链中,等待后续使用
configuration.addInterceptor(interceptorInstance);
}
}
}
configuration.addInterceptor做得操作很简单
包装
上面讲了插件的注册,最后调用的是configuration.addInterceptor,最终调用的是InterceptorChain#addInterceptor
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<>();
/*
* 每当四大对象创建时,都会执行此方法
* 满足拦截条件,则返回Plugin代理,否则返回原对象
* @param target Mybatis四大对象之一
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 调用每个插件的plugin方法,判断是否需要代理
target = interceptor.plugin(target);
}
return target;
}
// 将拦截器添加interceptors集合中存起来
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
} public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
} }
我们案例是拦截StatementHandler,所以也以此为例
/*
* 这是创建StatementHandler的方法
* Configuration#newStatementHandler
*/
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 可以看到创建完StatementHandler之后,会调用InterceptorChain的pluginAll方法
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
那么我们再仔细分析下
pluginAll
方法,pluginAll
调用的是每个插件的plugin
方法default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
可以看到,最终调用的是
Plugin.*wrap*
/*
* Plugin#wrap
* 判断是否满足插件的拦截条件,是则返回代理类,否则返回原对象
*/
public static Object wrap(Object target, Interceptor interceptor) {
// 获取插件的拦截信息(就是获取@Intercepts注解的内容)
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 判断是否满足拦截条件
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 满足拦截条件则返回Plugin代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 不满足则返回原对象
return target;
}
调用
在上一个
包装
步骤提到,满足条件会返回代理对象,即调用StatementHandler
的所有方法,都会经过Plugin
的invoke
方法,去看看// Plugin#invoke
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)) {
// 满足拦截条件,则调用插件的intercept方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
最新文章
- ECMAScript toString() 方法
- [6]Telerik TreeView 复选框
- CSS重置代码和常用公共代码
- OC项目中使用Swift
- 杭电1010Tempter of the Bone
- Jmail发送邮件与带附件乱码解决办法
- win32键盘记录 -- 自定义窗口类
- 纯css实现京东导航菜单
- ant使用
- PDF安全模式破解的简单办法
- CustomScrollView + slivers + SliverAppBar
- 实验二:Linux下Xen环境的安装
- HDU 3251 Being a Hero(最小割+输出割边)
- 移动端网页开发 meta 之 viewport
- Windows 下 MySql 5.7.20安装及data和my.ini文件的配置(转)
- 深入学习Make命令和Makefile(上)
- html+css小总结
- Sublime Text 自动换行
- oracle环境变量详解
- 详细介绍如何在Eclipse中使用SVN
热门文章
- CSS常用技术
- 如何彻底禁止 macOS Monterey 自动更新,去除更新标记和通知
- 关键字 global和nonlocal
- 【面试普通人VS高手】Spring 中Bean的作用域有哪些?
- 【js奇妙说】如何跟非计算机从业者解释,为什么浮点数计算0.1+0.2不等于0.3?
- 如何使用picGo+typora配置云笔记
- 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入
- SPFA 最短路算法
- BI 如何让SaaS产品具有 “安全感”和“敏锐感”(上)
- python+selenium 自动化测试——显式等待详解