mybatis 源码分析(五)Interceptor 详解
本篇博客将主要讲解 mybatis 插件的主要流程,其中主要包括动态代理和责任链的使用;
一、mybatis 拦截器主体结构
在编写 mybatis 插件的时候,首先要实现 Interceptor 接口,然后在 mybatis-conf.xml 中添加插件,
<configuration>
<plugins>
<plugin interceptor="***.interceptor1"/>
<plugin interceptor="***.interceptor2"/>
</plugins>
</configuration>
这里需要注意的是,添加的插件是有顺序的,因为在解析的时候是依次放入 ArrayList 里面,而调用的时候其顺序为:2 > 1 > target > 1 > 2;(插件的顺序可能会影响执行的流程)更加细致的讲解可以参考 QueryInterceptor 规范 ;
然后当插件初始化完成之后,添加插件的流程如下:
首先要注意的是,mybatis 插件的拦截目标有四个,Executor、StatementHandler、ParameterHandler、ResultSetHandler:
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;
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里使用的时候都是用动态代理将多个插件用责任链的方式添加的,最后返回的是一个代理对象; 其责任链的添加过程如下:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
最终动态代理生成和调用的过程都在 Plugin 类中:
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // 获取签名Map
Class<?> type = target.getClass(); // 拦截目标 (ParameterHandler|ResultSetHandler|StatementHandler|Executor)
Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // 获取目标接口
if (interfaces.length > 0) {
return Proxy.newProxyInstance( // 生成代理
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
这里所说的签名是指在编写插件的时候,指定的目标接口和方法,例如:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
...
}
}
这里就指定了拦截 Executor 的具有相应方法的 update、query 方法;注解的代码很简单,大家可以自行查看;然后通过 getSignatureMap 方法反射取出对应的 Method 对象,在通过 getAllInterfaces 方法判断,目标对象是否有对应的方法,有就生成代理对象,没有就直接反对目标对象;
在调用的时候:
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)) { // 判断这个调用的方法是否在拦截范围内
return interceptor.intercept(new Invocation(target, method, args)); // 在目标范围内就拦截
}
return method.invoke(target, args); // 不在目标范围内就直接调用方法本身
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
二、PageHelper 拦截器分析
mybatis 插件我们平时使用最多的就是分页插件了,这里以 PageHelper 为例,其使用方法可以查看相应的文档 如何使用分页插件,因为官方文档讲解的很详细了,我这里就简单补充分页插件需要做哪几件事情;
使用:
PageHelper.startPage(1, 2);
List<User> list = userMapper1.getAll();
PageHelper 还有很多中使用方式,这是最常用的一种,他其实就是在 ThreadLocal 中设置了 Page 对象,能取到就代表需要分页,在分页完成后在移除,这样就不会导致其他方法分页;(PageHelper 使用的其他方法,也是围绕 Page 对象的设置进行的)
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page<E>(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}
主要实现:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
dialect.afterAll();
}
}
}
}
- 首先可以看到拦截的是 Executor 的两个 query 方法(这里的两个方法具体拦截到哪一个受插件顺序影响,最终影响到 cacheKey 和 boundSql 的初始化);
- 然后使用 checkDialectExists 判断是否支持对应的数据库;
- 在分页之前需要查询总数,这里会生成相应的 sql 语句以及对应的 MappedStatement 对象,并缓存;
- 然后拼接分页查询语句,并生成相应的 MappedStatement 对象,同时缓存;
- 最后查询,查询完成后使用 dialect.afterPage 移除 Page对象
mybatis 源码分析(五)Interceptor 详解的更多相关文章
- vuex 源码分析(五) action 详解
action类似于mutation,不同的是Action提交的是mutation,而不是直接变更状态,而且action里可以包含任意异步操作,每个mutation的参数1是一个对象,可以包含如下六个属 ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
- nginx源码分析线程池详解
nginx源码分析线程池详解 一.前言 nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...
- vuex 源码分析(六) 辅助函数 详解
对于state.getter.mutation.action来说,如果每次使用的时候都用this.$store.state.this.$store.getter等引用,会比较麻烦,代码也重复和冗余,我 ...
- Golang源码分析之目录详解
开源项目「go home」聚焦Go语言技术栈与面试题,以协助Gopher登上更大的舞台,欢迎go home~ 导读 学习Go语言源码的第一步就是了解先了解它的目录结构,你对它的源码目录了解多少呢? 目 ...
- Tomcat源码分析 | 一文详解生命周期机制Lifecycle
目录 什么是Lifecycle? Lifecycle方法 LifecycleBase 增加.删除和获取监听器 init() start() stop() destroy() 模板方法 总结 前言 To ...
- Java 容器源码分析之集合类详解
集合类说明及区别 Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └W ...
- Cloudera Impala源码分析: SimpleScheduler调度策略详解包括作用、接口及实现等
问题导读:1.Scheduler任务中Distributed Plan.Scan Range是什么?2.Scheduler基本接口有哪些?3.QuerySchedule这个类如何理解?4.Simple ...
- Mybatis源码分析之Cache二级缓存原理 (五)
一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...
- MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
随机推荐
- Spring 核心技术(4)
接上篇:Spring 核心技术(3) version 5.1.8.RELEASE 1.4.2 依赖关系及配置详情 如上一节所述,你可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者 ...
- CF1195C Basketball Exercise (dp + 贪心)
题解出处:https://www.luogu.org/problemnew/solution/CF1195C 很水的一道C题……目测难度在黄~绿左右.请各位切题者合理评分. 注意到可以选择的球员编号是 ...
- spring boot入门篇
Spring Boot[快速入门] Spring Boot 概述 Build Anything with Spring Boot:Spring Boot is the starting point ...
- Android Studio电脑不支持HAXM的解决办法
Intel HAXM is required to run this AVD. Your CPU does not support required features (VT-x or SVM). U ...
- webstorm和git安装后,terminal输入git命令,提示'git' 不是内部或外部命令
最近换了工作,拿到电脑重新搭建编程环境,踩了好多git和sourcetree的坑,就是一直拉不下来代码,晕·~~经过几天的爬坑,终于可以拉取推送代码了!!! 问题:webstorm和git都安装完成, ...
- LVS + Keepalived + Nginx基于DR模式构建高可用方案
在大型网站中一般服务端会做集群,同时利用负载均衡器做负载均衡.这样有利于将大量的请求分散到各个服务器上,提升网站的响应速度.当然为了解决单点故障的问题,还会做热备份方案.这里演示利用LVS做负载均衡器 ...
- Docker Toolbox安装
公司最近搭建docker环境,其中会遇到一些问题,在这里记录一下. 先来了解一下docker 一.基本概念 1.Docker中基本概念镜像(Image) 提到镜像,有对操作系统有一定认知的都知道,镜像 ...
- Git应用之eclipse解决冲突代码
最近上班公司框架换成了微服务下面是eclipse 对代码进行管理 1.冲突代码 如果两个人在一个项目上同一文件上更改代码就会出现冲突现象 先用NewFile.jsp 文件做演示 打开eclipse从 ...
- Appium+python自动化(二十九)- 模拟手指在手机上多线多点作战 - 多点触控(超详解)
简介 在网页中我们经常使用缩放操作来便利的查看具体的信息,在appium中使用MultiAction多点触控的类来实现.MultiAction是多点触控的类,可以模拟用户多点操作.主要包含加载add( ...
- 每天用SpringBoot,还不懂RESTful API返回统一数据格式是怎么实现的?
上一篇文章RESTful API 返回统一JSON数据格式 说明了 RESTful API 统一返回数据格式问题,这是请求一切正常的情形,这篇文章将说明如何统一处理异常,以及其背后的实现原理,老套路, ...