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至少 ... 
随机推荐
- 更换国内镜像源进行pip安装
			Linux中当我们需要安装某个模块时(比如tensorflow2.0.0),常见有三种方法: pip install tensorflow==2.0.0 pip install https://pyp ... 
- Spring Authorization Server 实现授权中心
			Spring Authorization Server 实现授权中心 源码地址 当前,Spring Security 对 OAuth 2.0 框架提供了全面的支持.Spring Authorizati ... 
- 现代 CSS 解决方案:CSS 数学函数
			在 CSS 中,其实存在各种各样的函数.具体分为: Transform functions Math functions Filter functions Color functions Image ... 
- 『现学现忘』Git基础 — 25、git log命令参数详解
			目录 1.git log命令说明 2.git log命令参数 (1)不带参数 (2)常用显示参数 (3)--pretty参数 (4)--date=参数 (5)筛选参数 git log命令主要用于查看G ... 
- MyBatisPlus详解
			1.MyBatisPlus概述 需要的基础:MyBatis.Spring.SpringMVC 为什么要学习?MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成! 简介 ... 
- 个人冲刺(一)——体温上报app(一阶段)
			任务:完成了体温上报app的整体页面布局 activity_main.xml <?xml version="1.0" encoding="utf-8"?& ... 
- Pandas:添加修改、高级过滤
			1.添加修改数据 Pandas 的数据修改是进行赋值,先把要修改的数据筛选出来,然后将同结构或者可解包的数据赋值给它: 修改数值 df.Q1 = [1, 3, 5, 7, 9] * 20 # 就会把值 ... 
- 直观比较 popcount 的效率差异
			问题 求 \(\sum\limits_{i=1}^{3\times 10^8} popcount(i)\) . 仅考虑在暴力做法下的效率. 枚举位 __builtin_popcount #includ ... 
- JAVA - 线程同步和线程调度的相关方法
			JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ... 
- NOI Online 2022 一游
			NOI Online 2022 一游 TG 啊,上午比提高,根据去年的经验,题目配置估计那至少一黑 所以直接做 1 题即可.(确信) 总体:估分 140,炸了但没完全炸 奇怪的过程 开题:3 2 1 ... 
