一、前言

Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

二、会被拦截的接口

Mybatis 允许在映射语句执行过程中的某一点进行拦截调用。

默认情况下,Mybatis允许使用插件来拦截的接口和方法包括以下几个:

序号 接口 方法 描述
1 Executor update、query、flushStatements、commit、rollback、getTransaction、close、isClosed

拦截执行器的方法

2 ParameterHandler getParameterObject、setParameters

拦截参数的处理

3 ResultSetHandler handleResultSets、handleCursorResultSets、handleOutputParameters

拦截结果集的处理

4 StatementHandler prepare、parameterize、batch、update、query

拦截Sql语法构建的处理

Mybatis是通过动态代理的方式实现拦截的,阅读此篇文章需要先对Java的动态代理机制有所了解。可以参考博客《彻底理解java动态代理》

三、Mybatis四大接口

竟然Mybatis是对四大接口进行拦截的,那我们药先要知道Mybatis的四大接口对象 Executor, StatementHandler, ResultSetHandler, ParameterHandler。

图1-1   Mybatis框架执行过程

Mybatis插件能够对四大对象进行拦截,包括对Mybatis一次会话的所有操作进行拦截。可见Mybatis的插件的强大。

序号 接口 解读
1 Executor

是Mybatis的内部执行器。

它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射。

另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。

2 StatementHandler

是Mybatis直接和数据库执行sql脚本的对象。

另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。

3 ParameterHandler

是Mybatis实现Sql入参设置的对象。

这里,使用插件可以改变我们Sql的参数默认设置。

4 ResultSetHandler

是Mybatis把ResultSet集合映射成POJO的接口对象。

我们可以定义插件对Mybatis的结果集自动映射进行修改。

四、插件Interceptor

Mybatis的插件实现要实现Interceptor接口,我们看下这个接口定义的方法。

public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}

这个接口只声明了三个方法。

1.setProperties

在Mybatis的配置文件中配置插件时,可通过此方法来传递参数给插件。

如,在mybatis-config.xml中,一般情况下,拦截器的配置如下:

<plugins>
<!-- 1.interceptor属性为拦截器实现类的全类名 -->
<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
<!-- 2.通过property标签来配置参数,配置的参数在拦截器初始化时会通过setProperties方法传递给拦截器。 在拦截器中可以很方便的通过Properties取得配置的参数值 -->
<property name="prop1" value="value1" />
<property name="prop2" value="value2" />
</plugin>
</plugins>

2.plugin

此方法的参数target就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用 ???。

该方法的实现很简单,只需要调用Mybatis提供的Plugin(org.apache.ibatis.plugin.Plugin)类的wrap静态方法就可以通过Java的动态代理拦截目标对象。

这个方法的通常实现代码如下:

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

Plugin.wrap方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,只有匹配的情况下才会使用动态代理拦截目标对象,因此在上面的实现方法中不必做额外的判断逻辑。

来看一个稍微复杂一点的例子。

    @Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
if (target instanceof Executor) {
final Executor e = (Executor) target;
Executor executor = new Executor() {
public int update(MappedStatement ms, Object parameter) throws SQLException {
return e.update(ms, parameter);
} public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
return e.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
} public List<BatchResult> flushStatements() throws SQLException {
return e.flushStatements();
} public void commit(boolean required) throws SQLException {
e.commit(required);
} public void rollback(boolean required) throws SQLException {
e.rollback(required);
} public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
BoundSql boundSql) {
IRequest request = RequestHelper.getCurrentRequest(true);
boundSql.setAdditionalParameter("request", request);
return e.createCacheKey(ms, parameterObject, rowBounds, boundSql);
} public boolean isCached(MappedStatement ms, CacheKey key) {
return e.isCached(ms, key);
} public void clearLocalCache() {
e.clearLocalCache();
} public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
Class<?> targetType) {
e.deferLoad(ms, resultObject, property, key, targetType);
} public Transaction getTransaction() {
return e.getTransaction();
} public void close(boolean forceRollback) {
e.close(forceRollback);
} public boolean isClosed() {
return e.isClosed();
} public void setExecutorWrapper(Executor executor) {
e.setExecutorWrapper(executor);
}
}; return executor;
// return Plugin.wrap(executor, this);
}
return target;
}

上述代码中对匹配条件做了进一步的细化

3.intercept

此方法是Mybatis运行时要执行的拦截方法,

通过该方法的参数invocation可以得到很多有用的信息。

@Override
public Object intercept(Invocation invocation) throws Throwable{
Object target = invocation.getTarget();
Method method = invocation.getMethod();
Object[] args = invocation.getArgs();
Object result = invocation.proceed(); return result;
}

通过调用 invocation.proceed();可以执行被拦截对象真正的方法。proceed()方法实际上执行了method.invoke(target,args)方法、

4.多个插件的调用顺序

当配置多个拦截器时,Mybatis会遍历所有拦截器,按顺序执行拦截器的plugin方法,被拦截的对象就会被层层代理。

在执行拦截对象的方法时,会一层一层地调用拦截器,拦截器通过invocation.proceed()调用下一层的方法,直到真正的方法被执行。方法执行的结果会从最里面开始向外一层层返回,所以如果存在按顺序配置的ABC三个签名相同的拦截器,Mybatis会按照 C->B->A-> target.proceed() -> A->B->C

五、拦截器注解

除了需要实现拦截器接口外,还需要给实现类配置以下的拦截器注解:

(1)@Intercepts

(2)@Signature

使用这两个注解可以用来配置拦截器要拦截的接口。

1.注解说明

以拦截 ResultSetHandler 接口的 handleResultSets 方法为例,配置签名如下。

@intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)
})
public class ResultSetInterceptor implements Interceptor

@Signature 注解主要包含以下三个属性:

(1)type      :设置拦截的接口,可选值是前面提到的四个接口

(2)method :设置拦截接口中的方法名,可选值是前面4个接口对应的方法,需要和接口匹配。

(3)args      :设置拦截器的参数类型

六、参考资料

1.Mybatis插件原理

Mybatis_总结_06_用_插件开发的更多相关文章

  1. Mybatis_总结_03_用_动态SQL

    一.前言 MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦.例如拼接时要确保不能忘记添加必要的空格,还 ...

  2. GEF入门实例_总结_06_为编辑器添加内容

    一.前言 本文承接上一节:GEF入门实例_总结_05_显示一个空白编辑器 在上一节我们为我们的插件添加了一个空白的编辑器,这一节我们将为此编辑器添加内容. 二.GEF的MVC模式 在此只简单总结一下, ...

  3. Eclipse插件开发_异常_01_java.lang.RuntimeException: No application id has been found.

    一.异常现象 在运行RCP程序时,出现 java.lang.RuntimeException: No application id has been found. at org.eclipse.equ ...

  4. Eclipse插件开发_学习_00_资源帖

    一.官方资料 1.eclipse api 2.GEF Developer's Guide 二. 精选资料 1.开发 Eclipse 插件 2.Eclipse, RCP, Plugin and OSGi ...

  5. [刘阳Java]_什么是MyBatis_第1讲

    1.什么MyBatis,我们先通过百度百科先进行一个简单的了解 MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation ...

  6. 记录使用MyBatis_错误_警告_异常

    1.使用MyBatis要非常仔细检查自己的sql语句有没有写错. jdbcType错误,有可能在控制台显示一个 builderException.

  7. 壹度DIY_微信小程序组件_小程序插件开发

    开源免费插件,diy特有的页面机制,搭配30+自定义组件,让你的站点每一个页面都可以完全自定义,可无缝对接任意小程序,如有疑问加入qq壹度小程序交流群:302866773:或wx:liu2417301 ...

  8. 分布式_理论_06_ 一致性算法 Raft

    一.前言 五.参考资料 1.分布式理论(六)—— Raft 算法 2.分布式理论(六) - 一致性协议Raft

  9. Eclipse插件开发_学习_02_GEF入门实例

    一.前言 这一节,我们将会创建一个GEF入门实例 二.新建RCP项目 1. New 一个 Plug-in Project 2.输入项目名 项目名:com.ray.gef.helloworld 3.Co ...

随机推荐

  1. (4.8)SQL Server DAC——专用管理员连接

    SQL Server DAC——专用管理员连接 默认情况下,只有本地可以使用DAC连接,但也可以开启远程DAC sp_configure ; go reconfigure with override; ...

  2. [不常用] - CSRF(跨站点请求伪造)

    CSRF,Cross Site Request Forgery,即跨站点请求伪造.   这种攻击是指,在用户正常登录系统以后,攻击者诱使用户访问一些非法链接,以执行一些非法操作. 比如:如果删除用户操 ...

  3. Xshell访问kali配置

    1.安装虚拟机VMware Workstation12 PRO 2.在虚拟机上安装kali2.0 3.查看liunx的ip地址ifconfig 4.端口 协议 (1)RDP协议(桌面协议)3389端口 ...

  4. yii2弹出层

    bootstrap http://getbootstrap.com/javascript/#modals https://github.com/lichunqiang/yii2-sweet-submi ...

  5. Capslock and Esc

    将Caps Lock转换成Esc(windows and linux) 1. linux 下将Caps Lock 转换成Esc 作为一个vimer,Caps Lock对我(还有其他很多人)来说根本就是 ...

  6. 生信概念之global alignment VS local alignment

  7. 深入理解JVM1

    1 Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言.Java类文件格式.Java虚拟机和Java应用程 ...

  8. 后向传播算法“backpropragation”详解

    为什么要使用backpropagation? 梯度下降不用多说,如果不清楚的可以参考梯度下降算法. 神经网络的参数集合theta,包括超级多组weight和bais. 要使用梯度下降,就需要计算每一个 ...

  9. tree 命令【转】

    本文转载自:http://www.jb51.net/LINUXjishu/283874.html linux下怎么用tree命令以树形结构显示文件目录结构?tree命令可以以树形结构显示文件目录结构, ...

  10. 深入理解Node.js中的垃圾回收和内存泄漏的捕获

    深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...