原理

mybatis插件类型

plugins

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

那么 这些插件的执行顺序是如何的呢?

引入mybatis插件

maven依赖(略)

详见: gitee

配置插件

    <!-- 配置SqlSessionFactory对象 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- ..... --><property name="configLocation" value="classpath:mybatis-config.xml"/></bean>

自定义插件

插件的方法内容很简单,即打印当前方法的签名

 @Intercepts({@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class })
})
@Slf4j
public class Executor_Interceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("---------------------"+this.getClass().getSimpleName() +"::"+invocation.getMethod().getName());return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}

同理,以此注入如下的插件:

// Executor2_Interceptor ,Executor3_Interceptor 同  Executor_Interceptor//ParameterHandler 监听setParameters方法
@Intercepts({@Signature( type= ParameterHandler.class,method = "setParameters", args = {PreparedStatement.class})
})
public class ParameterHandler_Interceptor implements Interceptor  {}//ResultSetHandler 监听 handleResultSets 方法
@Intercepts({@Signature( type= ResultSetHandler.class,method = "handleResultSets", args = {Statement.class}),
})
public class ResultSetHandler_Interceptor implements Interceptor {}//StatementHandler 监听 prepare和query方法
@Intercepts({@Signature( type= StatementHandler.class,method = "prepare", args = {Connection.class,Integer.class}),@Signature( type= StatementHandler.class,method = "query", args = {Statement.class,ResultHandler.class})
})
public class StatementHandler_Interceptor implements Interceptor {}

同类型插件的执行顺序

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><plugins><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor2_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor3_Interceptor"></plugin></plugin></plugins>
</configuration>

执行查询方法–日志

结论
相同类型的plugins:按照 注册的 逆序,执行。

不同类型插件的执行顺序

mybatis-config.xml

<plugins><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.ParameterHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.ResultSetHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.StatementHandler_Interceptor"></plugin>
</plugins>

执行查询方法–日志

猜测
不同类型的plugins:按照 是否注册的顺序影响呢?

随机顺序-不同plugin

  <plugins><plugin interceptor="cn.jhs.framework.mybatis.plugins.ResultSetHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.ParameterHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.StatementHandler_Interceptor"></plugin><plugin interceptor="cn.jhs.framework.mybatis.plugins.Executor_Interceptor"></plugin></plugins>

此时的执行结果,跟上述结果相同

结论
总体顺序
Executor -> StatementHandler ->ParameterHandler -> ResultSetHandler
但是部分-方法的顺序,不受上述规则的控制。如日志中的StatementHandler.prepare()和StatementHandler.query()



源码分析

我们都知道,XXMapper#selectXXX(); 转换为 DefaultSqlSession#selectList(); 下面从DefaultSqlSession入手,分析,上述插件的执行顺序的原理。

构建DefaultSqlSession — 初始化


--DefaultSqlSessionFactory#openSessionFromDataSource()
----configuration#newExecutor();                //1.    创建 executor
------executor = new SimpleExecutor();             //1.1   创建SimpleExecutor
------executor = new CachingExecutor(executor);    //1.2   包装成: CachingExecutor
------executor = (Executor) interceptorChain.pluginAll(executor);  //1.3   使用 interceptorChain.代理executor
----------for-each -> interceptor.plugin(target);                    //1.3.1 内部调用: Plugin.wrap(target, this);
----------Plugin#wrap(target, this);                                //1.3.1.1 生成executor代理对象public static Object wrap(Object target, Interceptor interceptor) {//a. 获取代理interceptor所有签名的<接口,Set<接口methods>>Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//b. target = CachingExecutor;Class<?> type = target.getClass();//c. type(CachingExecutor.class)与interceptor签名接口 交集;  // 此时CachingExecutor实现了Executor接口, 故只有@Signature中包含Executor接口的interceptor,会生成代理对象;Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//d. 如果存在交集, 则生成代理对象if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}//e. 如果不存在交集,则返回原始对象return target;}
------返回一个可能存在多重Plugin..warp的(CachingExecutor(SimpleExecutor)) 对象;
-----new DefaultSqlSession(configuration, executor, autoCommit);    // 2.  构造DefaultSqlSession对象

此时构建出一个DefaultSqlSession对象,它的内容如下:

public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor; // 仅包含 plugins中,@Signature中包含Executor接口的interceptorpublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);}
}

执行查询

接上一节内容,所有的查询都会转换为executor.query, 此时的executor为@Signature中包含Executor接口的interceptor生成的Plugin代理对象

1. 执行Plugin<Executor.class>

Plugin执行代理方法的逻辑如下。 此时的Plugin代理的是Executor

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Set<Method> methods = signatureMap.get(method.getDeclaringClass());    //1.1 查看当前Plugin支持的方法类型setif (methods != null && methods.contains(method)) {                  //1.2 检验执行mapper方法类型,与当前Plugin设置的方法 是否一致return interceptor.intercept(new Invocation(target, method, args));  //1.3. 链式调用*执行插件interceptor.intercept方法}return method.invoke(target, args); //1.4 执行原始对象 CachingExecutor();}

2. 执行CachingExecutor#query()

上一步执行完成之后,最终执行真实对象查询方法CachingExecutor#query.

-------delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //delegate = SimpleExecutor //SimpleExecutor extends BaseExecutor//delegate.query() 执行 BaseExecutor#query(), 而BaseExecutor内部调用抽象方法doQuery,由 SimpleExecutor实现
---------SimpleExecutor#doQuery(); // 2.1
-----------StatementHandler handler = configuration.newStatementHandler() // 2.1.1
-------------StatementHandler statementHandler = new RoutingStatementHandler(); //2.1.1.1 构造 RoutingStatementHandler
---------------delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); //2.1.1.1.1 内部delegate//delegate 构造内容public class PreparedStatementHandler extends BaseStatementHandler {protected BaseStatementHandler() { this.parameterHandler = configuration.newParameterHandler(); //a. 内部调用创建 ParameterHandlerthis.resultSetHandler = configuration.newResultSetHandler(); //b. 内部调用创建 ResultSetHandler}}
-------------statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); //2.1.1.2 最终通过Plugin.warp(=RoutingStatementHandler);它的delegate类型为PreparedStatementHandler
-----------Statement stmt = prepareStatement(handler, ms.getStatementLog());// 2.1.2
-------------Connection connection = getConnection(statementLog);          // 2.1.2.1 获取数据库连接
-------------stmt = handler.prepare(connection, transaction.getTimeout()); // 2.1.2.2 PreparedStatementHandler->BaseStatementHandler#prepare ;
-------------handler.parameterize(stmt);                                    // 2.1.2.3 PreparedStatementHandler#parameterize ; -----------return handler.<E>query(stmt, resultHandler);// 2.1.3
------------PreparedStatementHandler#query()public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); //2.1.3.1 执行psreturn resultSetHandler.<E> handleResultSets(ps); //2.1.3.2  resultSetHandler 渲染执行结果}

关键类图

执行顺序

  • handler.prepare : StatementHandler
  • handler.parameterize: StatementHandler + ParameterHandler
  • handler.<E>query : StatementHandler + ParameterHandler
  • resultSetHandler.<E> handleResultSets(ps) : ResultSetHandler

mybatis插件的执行顺序相关推荐

  1. 后端技术:mybatis插件原理详解

    关注"Java后端技术全栈" 回复"面试"获取全套面试资料 上次发文说到了如何集成分页插件MyBatis插件原理分析,看完感觉自己better了,今天我们接着来 ...

  2. 框架源码专题:Mybatis启动和执行流程、源码级解析

    文章目录 1. Mybatis 启动流程 步骤一: 把xml配置文件解析成Configuration类 步骤二: 创建SqlSession会话 mybatis的三种执行器 步骤三: 在sqlSessi ...

  3. SpringCloud或SpringBoot+Mybatis-Plus利用mybatis插件实现数据操作记录及更新对比

    引文 本文主要介绍如何使用mybatis插件实现拦截数据库操作并根据不同需求进行数据对比分析,主要适用于系统中需要对数据操作进行记录.在更新数据时准确记录更新字段 核心:mybatis插件(拦截器). ...

  4. WordPress工作原理之程序文件执行顺序

    在了解WordPress挂载机制时,一直有一个疑惑,到底是WordPress的内核源文件先执行还是主题文件里functions.php文件先执行.为了解决这个问题,想了解WordPress的工作原理, ...

  5. c++map的使用_mybatis源码 | mybatis插件及动态代理的使用

    学习背景 最近公司在做一些数据库安全方面的事情,如数据库中不能存手机号明文,不能存身份证号明文, 但是项目已经进行了好几个月了, 这时候在应用层面去改显然不太现实, 所以就有了Mybatis的自定义插 ...

  6. 写过Mybatis插件?那说说自定义插件是如何加载的吧?

    大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外. 我们从插件配置.插件编写.插件运行原理.插件注册与执行拦截的时机.初始化插件.分页插件的原理等六个方面展开阐述. 1 ...

  7. TestNG测试框架之测试用例的执行顺序分析

    既然是讨论执行顺序问题,那么用例肯定是批量执行的,批量执行的方法有mvn test.直接运行testng.xml文件,其中直接运行testng.xml文件的效果与pom文件中配置执行testng.xm ...

  8. 美团一面:你既然写过Mybatis插件,能给我说说它底层是怎么加载一个自定义插件的吗?...

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达今日推荐:2020年7月程序员工资统计,平均14357元,又跌了,扎心个人原创100W+访问量博客:点击前往,查看更多 大多 ...

  9. MyBatis(四)MyBatis插件原理

    MyBatis插件原理 MyBatis对开发者非常友好,它通过提供插件机制,让我们可以根据自己的需要去增强MyBatis的功能.其底层是使用了代理模式+责任链模式 MyBatis官方https://m ...

  10. Pytest-ordering自定义用例执行顺序

    我们一般在做自动化测试时,用例设计之间应该是可以相互独立执行的,没有一定的前后依赖关系的,如果我们真的有前后依赖,想指定用例的先后顺序,可以用到pytest-ordering插件解决这个问题 1.安装 ...

最新文章

  1. MySQL数字辅助表
  2. 一次生产的 JVM 优化案例
  3. linux samba代码,Linux下Samba服务器源码安装及配置
  4. 华三云:不做开源的投机者
  5. openwrt使用linux内核版本,降低OpenWRT的Linux内核版本
  6. html字符串替换src,替换html字符串中img标签src的值.
  7. 目标跟踪算法的分类(二)
  8. 数字化医院网络终端安全管理
  9. 推荐一款超级好用的开源项目画图工具
  10. 修改程序版本工具(ResHacker)使用说明20140902
  11. 从零开始内网渗透学习
  12. python提取XML信息保存为txt
  13. docker提交比赛记录
  14. 关于opencv fitLine直线拟合得斜率及截距
  15. HttpClient4.5.6设置代理以及代理验证(用户名和密码)
  16. [NAS] Synology (群晖) DSM 7.X 挂载NTFS硬盘
  17. 服务器如何安装windows7系统,安装Windows 7系统
  18. 优化嵌入式软件的几点技巧
  19. pandas交叉表与透视表pd.crosstab()和pd.pivot_table()函数详解
  20. 使用idea打包war包

热门文章

  1. 基于java水果网店管理系统计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
  2. Windows11彻底卸载Edge
  3. 【libmodbus-vs2019】测试使用
  4. 多种企业常用网管软件介绍及配置说明(带视频)
  5. python下载pandas库_Python中的pandas库
  6. python ev3图形化编程软件下载_【stm32图形化编程软件免费版下载】stm32图形化编程软件 v1.0 最新免费版-开心电玩...
  7. 个人设想中的TCAX GUI生成的带python脚本代码的ASS字幕文件
  8. TensorFlow实现图像风格迁移
  9. IE浏览器GET请求中文乱码
  10. 中国家庭追踪调查(CFPS)数据及问卷(2010-2018年)