一、前言

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. kubernetes 搭建教程

    http://blog.csdn.net/u011563903/article/details/71037093

  2. AFNetworking 和 ASIHTTPRequest

    在开发iOS应用过程中,如何高效的与服务端API进行数据交换,是一个常见问题.一般开发者都会选择一个第三方的网络组件作为服务,以提高开发效率和稳定性.这些组件把复杂的网络底层操作封装成友好的类和方法, ...

  3. Serv-u 外网访问内网的FTP服务器

    1. 背景简介 最近研究如何在内网搭架FTP服务器,同时要保证外网(公网)能访问的到.终成正果,但走了一些弯路,在此记下,以飨后人. 2. 基础知识 FTP 使用 2 个端口,一个数据端口和一个命令端 ...

  4. $用python处理Excel文档(2)——用xlsxwriter模块写xls/xlsx文档

    Refer:<python自动化运维:技术与最佳实践> 更多用法参考xlsxwriter官方文档:http://xlsxwriter.readthedocs.io/ 本文主要总结一下如何使 ...

  5. 【leetcode刷提笔记】Search Insert Position

    Given a sorted array and a target value, return the index if the target is found. If not, return the ...

  6. 【鸟哥的Linux私房菜】笔记1

    Linux是什么 从操作系统与cpu架构关系到linux  Richard Mathew Stallman GPL 关于GNU计划 Linux的发展 Linux的核心版本 Linux的特色 Linux ...

  7. 20145230《Java程序设计》第5周学习总结

    20145230 <Java程序设计>第5周学习总结 教材学习内容 本周主要学习的内容是关于异常处理的,感觉这部分内容对我们这种初学者 来说非常重要.举个例子,倘若你在编写一个Java程序 ...

  8. servlet原理分析

    一.Servlet简介 Servlet是sun公司提供的一门用于开发动态web资源的技术. Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向 ...

  9. 为什么可以Ping通IP地址,但Ping不通域名?

    能否ping通IP地址,与能否解析域名是两回事不能ping通IP地址,说明对方禁止ICMP报文或对方没有开机等解析域名只是将域名翻译成IP地址,不论该IP地址是否能够正常访问 问题是ping域名的时候 ...

  10. 【P2052】道路修建(树形+搜索)

    这个题看上去高大上,实际上就是一个大水题.怎么说呢,这个题思路可能比较难搞,代码实现难度几乎为0. 首先我们可以发现这是一棵树,然后问其中任意一条边左右两边的点的数量之差的绝对值,实际上,无论两边的点 ...