一、前言

MyBatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变MyBatis的默认行为(诸如SQL重写之类的),由于插件会深入到MyBatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现目标对象在执行目标方法之前进行拦截的效果。
插件介入指的是:创建过程中都会涉及到调用interceptChain.pluginAll()方法对四大对象进行重新包装,返回一个代理对象。
MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

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

插件开发是基于动态代理实现的,所有有必要对动态代理有所了解。

我们可以看一下MyBatis是怎么创建这四大接口对象的。找到源码BaseStatementHandler

this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

进入configuration类,下面几处都是在创建newParameterHandler,ResultSetHandler,StatementHandler这几个对象,在调用的过程中,大家都看到了都使用了interceptorChain.pluginAll方法分别对每一个对象进行了重新包装并返回

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;
}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;
}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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;
}

点进interceptorChain.pluginAll方法里面

/**
*每一个拦截器对目标类都进行一次代理
*@target
*@return 层层代理后的对象
**/
public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;
}

这一段代码可以看到:获取所有的Interceptor(拦截器),我们如果需要自定义拦截器就得实现Interceptor这个接口。然后调用interceptor.plugin(target);返回target包装之后的对象
所以,我们可以使用插件为目标对象创建一个代理对象,这跟我们学习Struts2的拦截器,Spring的AOP一样,其实都是动态代理,面向切面的编程。

下面我们通过案例为StatementHandler创建代理对象

二、案例

搭建一个新的Maven工程MyBatisPlugins

创建一个拦截器StatementHandlerInterceptor

/*** 定义插件StatementHandlerInterceptor* 告诉MyBatis当前插件用来拦截哪个对象的哪个方法*/
@Intercepts({@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
public class StatementHandlerInterceptor implements Interceptor {/*** 拦截目标对象的目标方法的执行*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 执行目标方法Object proceed = invocation.proceed();// 返回执行后的返回值return proceed;}/*** 包装目标对象 包装:为目标对象创建一个代理对象* 根据当前拦截的对象创建了一个动态代理对象。代理对象的InvocationHandler处理器为新建的Plugin对象* 官方推荐实现方式:Plugin.wrap(target, this);*/@Overridepublic Object plugin(Object target) {// 我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象System.out.println("StatementHandlerInterceptor...plugin:mybatis将要包装的对象" + target);Object wrap = Plugin.wrap(target, this);// 返回为当前target创建的动态代理return wrap;}/*** 将插件注册时的property属性设置进来*/@Overridepublic void setProperties(Properties properties) {System.out.println("插件配置的信息:"+properties);}}

每一个拦截器都必须实现上面的三个方法,其中:

  • Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
  • Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。
  • setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名   [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

将拦截器注册到全局配置文件中

打印测试一下,看看控制台打印效果

StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@4d639a35
StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@1d74b5ee
StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@27f5ad1c
StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@4229e6232017-08-20 12:35:29,825 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ?
2017-08-20 12:35:29,895 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
2017-08-20 12:35:29,949 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]

可以看到四大对象创建的时候都会调用这个拦截器因为都会被拦截下来,但是只有StatementHandler被创建了代理对象。这是为什么呢?大家可以看一下Plugin的源码

大家可以Debug一下源码,您会发现只有StatementHandler被创建了代理对象

三、总结

插件开发步骤如下:

  • 编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名
  • 在全局配置文件中注册插件

插件的原理:

  • 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
  • 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
  • 目标方法执行时依次从外到内执行插件的intercept方法

=======欢迎大家拍砖,小手一抖,多多点赞哟!=======

版权声明:本文为博主原创文章,允许转载,但转载必须标明出处。

MyBatis插件开发原理相关推荐

  1. Mybatis运行原理及源码解析

    Mybatis源码解析 一.前言 本文旨在mybatis源码解析,将整个mybatis运行原理讲解清楚,本文代码地址: https://github.com/lchpersonal/mybatis-l ...

  2. 深入理解 Mybatis 插件开发

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:风一样的码农 cnblogs.com/chenpi/p/10 ...

  3. 面试官:你分析过mybatis工作原理吗?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样 ...

  4. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  5. MyBatis运行原理(二)SqlSession对象创建过程分析

    PS:这篇博文承接上一篇: MyBatis运行原理(一)SqlSessionFactory对象创建过程分析 在上一篇博文中分析了SqlSessionFactory对象创建的过程,有了SqlSessio ...

  6. mybatis 插件原理

    [传送门]:mybatis 插件原理 转载于:https://www.cnblogs.com/virgosnail/p/10079838.html

  7. MyBatis(四)MyBatis插件原理

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

  8. mybatis返回null_面试官:你分析过mybatis工作原理吗?

    Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样才能怼回去.本文建立在Spring+SpringMVC+Mybatis整合的项目之上. 我将其工作原理分为六个部分: 读取核心配置文件 ...

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

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

最新文章

  1. CVPR2020论文解读:CNN合成的图片鉴别
  2. 这老哥把GPU当暖气用,省钱了!
  3. python字符串、列表和文件对象总结
  4. 2017蓝桥杯省赛---java---B---2(纸牌三角形)
  5. 清华大学-美团数字生活联合研究院成立
  6. java验证cron表达式_cron表达式
  7. 滤波电容、去耦电容、旁路电容的作用
  8. PHP验证码(画图)无法正常显示问题
  9. gluster分布式存储 入门篇
  10. 【图像隐写】基于matlab DWT数字水印嵌入+攻击+提取【含Matlab源码 1759期】
  11. 用ABAP编程破解世界上最难数独游戏
  12. 三年磨一剑——微信OCR轻松提取图片文字
  13. HTTP长连接和短连接
  14. 考研篇:如何在偶数年数学120+(刷同样的题,为什么有人的分数会更高?)
  15. 无根树的Prufer序列
  16. 想深入学习计算机需要看哪些经典书籍?
  17. 全球及中国生物制药产业盈利现状及竞争格局展望报告2021-2027年
  18. CES 2023:华硕轻薄本创新形态+硬核配置引领新创作时代
  19. Qt: Linux下生成.xlsx文件(excel表格文件)
  20. 浅析支付宝钱包支付流程

热门文章

  1. postgres定时备份数据库
  2. could not open java jre6 lib amd64 jvm.cfg
  3. 什么蓝牙耳机音质好?发烧友力荐四款好音质蓝牙耳机
  4. AD(AltiumDesigner)软板3D操作
  5. 关于pyCharm运行测试用例无法生成测试报告
  6. Selenium:设置无头浏览器
  7. Sparse R-CNN 论文精读
  8. 单点登录 cas 设置回调地址_cas客户端流程详解(源码解析)单点登录
  9. OpenLayers入门(二)
  10. 10秒去除WPS Office弹窗广告教程(2023.3.31最新)