《深入浅出MyBatis技术原理与实战》——7. 插件
在第6章讨论了四大运行对象的运行过程,在Configuration对象的创建方法里我们看到了MyBatis用责任链去封装它们。
7.1 插件接口
在MyBatis中使用插件,我们必须使用接口Interceptor,先来看看它的定义和各个方法的含义:


在接口各中,运用了3个方法,这3个方法的含义是:

这里看到了插件的骨架,这样的模式我们称为模版模式,就是提供一个骨架,并且告知骨架中的方法是干什么用的,由开发者来完成它。
7.2 插件的初始化
插件的初始化实在MyBatis初始化的时候完成的,这点可以通过XMLConfigBuilder中的代码知道:


在解析配置文件的时候,在MyBatis的上下文初始化过程中,就开始读入插件节点和我们配置的参数,同时使用反射技术生成对应的插件实例,然后调用插件方法中的setProperties方法,设置我们配置的参数,然后将插件实例保存到配置对象中,以便读取和使用它。所以插件的实例对象是一开始就被初始化的,而不是用到的时候才初始化的,我们使用它的时候,直接拿出来就可以了,这样有助于性能的提高。
再来看看插件在Configuration对象里是怎样保存的:

InterceptorChain在Configuration里面是一个属性,它里面有个addInterceptor方法:

显然,完成初始化的插件就保存在这个List对象里面等待将其取出使用。
7.3 插件的代理和反射设计
插件用的是责任链模式。就是一个对象,在MyBatis中可能是四大对象中的一个,在多个角色中传递,处在传递链上的任何角色都有处理它的机会。
MyBatis的责任链是由InterceptorChain去定义的,MyBatis在创建执行器时用到这样的代码:

来看看pluginAll()方法是如何实现的:

plugin方法是生成代理对象的方法,当它取出插件的时候是从Configuration对象中去取的。从第一个对象(四大对象中的一个)开始,将对象传递给了plugin方法,然后返回一个代理,如果存在第二个插件,那么就拿到第一个代理对象的代理,传递给plugin方法再返回第一个代理对象的代理......以此类推,有多少个拦截器就生成多少个代理对象。这样有多少个拦截器就生成多少个代理对象。
MyBatis中提供了一个常用的工具类来生成代理对象,它便是Plugin类。Plugin类实现了InvocationHandler接口,采用的是JDK动态代理,我们首先看看这个类的两个十分重要的方法:


看以看到它是一个动态代理对象,其中wrap方法为我们生成这个对象的动态代理对象。至于invoke方法,如果使用这个类为插件生成代理对象,那么代理对象在调用方法的时候就会进入到invoke方法中。在invoke方法中,如果存在签名的拦截方法,插件的intercept方法就会被我们在这里调用,然后就返回结果。如果不存在签名的方法,那么将直接调度我们要执行的方法。
我们创建一个Invocation对象,其构造方法的参数包括被代理对象、方法及其参数。Invocation对象进行初始化,它有一个proceed()方法:


这个方法就是调度被代理对象的真实方法。现在假设有n个插件,我们知道第一个传递的参数是四大对象本身,然后调用一次wrap方法产生第一个代理对象,而这里的反射就是反射四大对象本身的真实方法。如果有第二个插件,我们会将第一个代理对象传递给wrap方法,生成第二个代理对象,这里的反射就是指第一个代理对象的invoke方法,依次类推直至最后一个代理对象。如果每一个代理对象都调用这个proceed方法,那么最后四大对象本身的方法也会被调用,只是它会从最后一个代理对象的invoke方法运行到第一个代理对象的invoke方法,直至四大对象的真实方法。
7.4 常用的工具类——MetaObject
可以有效的读取或者修改一些重要的对象属性。它有3个方法常常用到:


我们可以通过使用它来给四大对象的某些属性赋值从而满足我们的需要。例如,拦截StatementHandler对象,我们需要先获取她要执行的SQL修改它的一些值。这个时候我们可以使用MetaObject,如:

拦截的StatementHandler实际上是RoutingStatementHandler对象,它的delegate属性才是真实服务的StatementHandler,真实的StatementHandler有一个属性BoundSql,它下面又有一个属性sql。所以才有了路径delegate.boundSql.sql。我们就可以通过这个路径获取或者修改对应运行时的SQL。
7.5 插件开发过程实例
限制每一条SQL返回数据的行数。限制的行数是个可配置的参数,业务可以根据自己的需要去配置。
7.5.1 确定需要拦截的签名
从Plugin源码中我们可以看到它需要注册签名才能够运行插件。签名需要确定一些要素。
(1) 确定需要拦截的签名

那么我们需要拦截的是StatementHandler对象,应该在预编译SQL之前,修改SQL使得返回数量被限制。
(2) 拦截方法和参数
查询是通过Executor调度StatementHandler来完成的。调度StatementHandler的prepare方法预编译SQL,于是我们需要拦截的方法便是prepare方法,在此之前完成SQL的重新编译。先来看看那StatementHandler接口的定义:

以上的任何方法都可以被拦截。从接口的定义而言,prepare方法有一个参数Connection对象,因此我们按如下代码来设计拦截器:

其中@Intercepts说明它是一个拦截器。@Signature是注册拦截器签名的地方,只有签名满足条件才能被拦截,type可以使四大对象中的一个,这里是StatementHandler。method代表要拦截四大对象的某一种接口方法,而args则表示该方法的参数。
7.5.4 插件实例
限制查询返回的数据量。需要拦截的是StatementHandler对象




在setProperties方法中可以读入配置给插件的参数,一个是数据库的名称,另外一个是限制的记录数。在plugin方法里,使用了MyBatis提供的类来生成代理对象。那么插件就会进入plugin的invoke方法,它最后会使用到拦截器的intercept方法。现在我们需要在MyBatis配置文件里面配置才能运行这个插件:

注意:


《深入浅出MyBatis技术原理与实战》——7. 插件的更多相关文章
- 《深入浅出MyBatis技术原理与实战》——6. MyBatis的解析和运行原理
MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程. 6.1 涉及的技术 ...
- 《深入浅出MyBatis技术原理与实战》——3. 配置
要注意的是上面那些层次是不能够颠倒顺序的,否则MyBatis在解析文件的时候就会出现异常. 3.1 properties元素 properties是一个属性配置元素,让我们能在配置文件的上下文中使用它 ...
- 《深入浅出MyBatis技术原理与实战》——4. 映射器,5. 动态SQL
4.1 映射器的主要元素 4.2 select元素 4.2.2 简易数据类型的例子 例如,我们需要统计一个姓氏的用户数量.应该把姓氏作为参数传递,而将结果设置为整型返回给调用者,如: 4.2.3 自动 ...
- 《深入浅出MyBatis技术原理与实战》——1.简介,2.入门
1. 简介 Java程序都是通过JDBC连接数据库,但是只定义了接口规范,具体的实现交给各个数据库厂商去实现,因为每个数据库都有其特殊性.所以JDBC是一种桥接模式. 这里为什么说JDBC是一种桥接模 ...
- 深入浅出MyBatis技术原理与实战
第1 章 MyBatis 简介..................................................................................... ...
- 深入浅出Mybatis技术原理与实战(杨开振)(带详细书签) PDF 下载 高清 完整版+源码
(杨开振) 源码 IDE eclipse 建表语句也在里面 电子书+源码地址
- 2017.2.9 深入浅出MyBatis技术原理与实践-第八章 MyBatis-Spring(二)-----配置文件详解
深入浅出MyBatis技术原理与实践-第八章 MyBatis-Spring(二) ------配置文件详解 8.2 MyBatis-Spring应用 8.2.1 概述 本文主要讲述通过注解配置MyBa ...
- 2MyBatis入门--深入浅出MyBatis技术原理与实践(笔记)
什么是 MyBatis ? MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...
- 3MyBatis配置--深入浅出MyBatis技术原理与实践(笔记)
XML 映射配置文件 configuration 配置 properties 属性 settings 设置 typeAliases 类型命名 typeHandlers 类型处理器 objectFact ...
随机推荐
- NOIP2017金秋冲刺训练营杯联赛模拟大奖赛第一轮Day2题解
上星期打的...题有点水,好多人都AK了 T1排个序贪心就好了 #include<iostream> #include<cstring> #include<cstdlib ...
- SPOJ - DETER3:Find The Determinant III (求解行列式)
Find The Determinant III 题目链接:https://vjudge.net/problem/SPOJ-DETER3 Description: Given a NxN matrix ...
- stout代码分析之十:c++11之move和forward
stout中大量使用了c++11的特性,而c++11中move和forward大概是最神奇的特性了. 左值和右值的区别 ; // a是左值,0是右值 int b = rand(); // b是左值,r ...
- 动态规划小结 - 二维动态规划 - 时间复杂度 O(n*n)的棋盘型,题 [LeetCode] Minimum Path Sum,Unique Paths II,Edit Distance
引言 二维动态规划中最常见的是棋盘型二维动态规划. 即 func(i, j) 往往只和 func(i-1, j-1), func(i-1, j) 以及 func(i, j-1) 有关 这种情况下,时间 ...
- Spring @Async的异常处理
楼主在前面的2篇文章中,分别介绍了Java子线程中通用的异常处理,以及Spring web应用中的异常处理.链接如下: Java子线程中的异常处理(通用) Spring web引用中的异常处理 今天, ...
- 奇偶排序Odd-even sort
又一个比较性质的排序,基本思路是奇数列排一趟序,偶数列排一趟序,再奇数排,再偶数排,直到全部有序 举例吧, 待排数组[6 2 4 1 5 9] 第一次比较奇数列,奇数列与它的邻居偶数列比较,如6和2比 ...
- asyncio 实现 aiohttp
#asyncio 没有提供http协议的接口 aiohttp import asyncio import socket from urllib.parse import urlparse async ...
- Flask中路由原理
在Flask内部使用两张表维护路由: url_map :维护URL规则和endpoint的映射 view_functions :维护endpoint和视图函数的映射. 以用户访问URL/home为例, ...
- Codeforces Round #494 (Div. 3)
刚好在考完当天有一场div3,就开了个小号打了,打的途中被辅导员喊去帮忙,搞了二十分钟-_-||,最后就出了四题,题解如下:题目链接:http://codeforces.com/contest/100 ...
- MongoDB 数据库(1)
数据库 MongoDB (芒果数据库) 数据存储阶段 文件管理阶段 (.txt .doc .xls) 优点 : 数据可以长期保存 可以存储大量的数据 使用简单 缺点 : 数据一致性差 数据查找修改不方 ...