Mybatis源码解读-插件
插件允许对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);
}
}
Mybatis源码解读-插件的更多相关文章
- MyBatis源码解读(3)——MapperMethod
在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在 ...
- MyBatis 源码分析 - 插件机制
1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...
- MyBatis源码解读之延迟加载
1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当 ...
- MyBatis 源码篇-插件模块
本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...
- spring IOC DI AOP MVC 事务, mybatis 源码解读
demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...
- Mybatis源码解读-SpringBoot中配置加载和Mapper的生成
本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...
- MyBatis源码解读(1)——SqlSessionFactory
在前面对MyBatis稍微有点了解过后,现在来对MyBatis的源码试着解读一下,并不是解析,暂时定为解读.所有对MyBatis解读均是基于MyBatis-3.4.1,官网中文文档:http://ww ...
- 【转】Mybatis源码解读-设计模式总结
原文:http://www.crazyant.net/2022.html?jqbmtw=b90da1&gsjulo=kpzaa1 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开 ...
- Mybatis源码解读-设计模式总结
虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...
随机推荐
- Spring 源码(14)Spring Bean 的创建过程(5)
到目前为止,我们知道Spring创建Bean对象有5中方法,分别是: 使用FactoryBean的getObject方法创建 使用BeanPostProcessor的子接口InstantiationA ...
- 场景实践:基于 IntelliJ IDEA 插件部署微服务应用
体验简介 阿里云云起实验室提供相关实验资源,点击前往 本场景指导您把微服务应用部署到 SAE 平台: 登陆 SAE 控制台,基于 jar 包创建应用 基于 IntelliJ IDEA 插件更新 SAE ...
- 对 Python 中 GIL 的一点理解
GIL(Global Interpreter Lock),全局解释器锁,是 CPython 为了避免在多线程环境下造成 Python 解释器内部数据的不一致而引入的一把锁,让 Python 中的多个线 ...
- Linux namespace技术应用实践--调用宿主机命令(tcpdump/ip/ps/top)检查docker容器网络、进程状态
背景 最近偶然听了几堂极客时间的云原生免费公开课程,首次接触到了Linux namespace技术,并了解到这正是现在风头正劲的容器技术基石,引起了自己探究一二的兴趣,结合课程+网络搜索+实践操作,也 ...
- Docker的网络
概述 docker使用Linux桥接网卡,在宿主机虚拟一个docker容器网桥(docker0),docker启动一个容器时会根 据docker网桥的网段分配给容器一个IP地址,称为Container ...
- Weakmap详解
先看一个例子 let obj = { name: 'toto' } // { name: 'toto' }这个对象能够被读取到,因为obj这个变量名有对它的引用 // 将引用覆盖掉 obj = nul ...
- SAP 实例 3 Context Menus
REPORT demo_dynpro_context_menu. DATA: field1 TYPE i VALUE 10, field2 TYPE p DECIMALS 4. DATA: prog ...
- Linux yum搭建私有仓库
搭建yum仓库需要两种资源: rpm包 rpm包的元数据(repodata) 搭建好仓库后需要使用三种网络协议共享出来 http或https ftp 范例: 使用http协议搭建私有仓库 (本示例使用 ...
- sql-DCL用户及权限管理及其他常用命令-oracle
DCL 用户管理 创建用户 create user 用户名 identified by 密码; 在oracle中要创建一个新的用户使用create user语句,一般是具有dba(数据库管理员)的权限 ...
- java中常见的锁
1.悲观锁 认为别的线程都会修改数据,二话不说先锁上 synchronized 2.乐观锁 乐观豁达,起初不操作.最后修改的时候比对一下版本,不一致再上锁 3.可重入锁 外层锁了之后,内层仍可以直接使 ...