前言

本文我们来学习mybatis插件的相关知识。

插件简介

一般开源框架都会提供插件或者其他形式的扩展点,供开发者自行扩展。这样做可以更灵活的适应实际的开发需求。mybatis同样提供了扩展机制,我们可以基于mybatis的插件扩展实现分页、监控等功能。由于插件和业务无关,业务也无法感知插件的存在,因此我们可以无感植入插件,增强功能。

Mybatis插件原理

mybatis有四大组件,分别是Executor、StatementHandler、ParameterHandler、ResultSetHandler。mybatis的插件机制也是基于这四大核心对象。mybatis的插件就是对这四大核心对象进行拦截,从而增强这四大对象的功能,增强功能本质上是借助于底层的动态代理实现的。

Mybatis所允许拦截的方法如下图所示:

简而言之,mybatis的插件就是一个拦截器,对四大对象进行拦截,使用动态代理增强功能。

插件执行步骤

mybatis中在四大对象创建的时候,每个创建出来的对象不是直接返回的,而是通过拦截器链interceptorChain.pluginAll(parameterHandler)来生成的。拦截器链中所有的拦截器调用interceptor.plugin(target)方法返回包装增强后的对象。mybatis插件就需要实现interceptor接口来编写增强逻辑。

以ParameterHandler 来说明,

1
2
3
4
5
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}

interceptorChain.pluginAll(parameterHandler)方法源码如下:

1
2
3
4
5
6
7
  public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}

interceptorChain保存了所有的拦截器interceptors,是mybatis初始化时创建的,调用拦截器链中的拦截器依次对目标进行拦截或增强。interceptor.plugin(target)中的target就是mybatis当中的四大对象,返回的target就是被重重代理过后的对象。

比如我们想要拦截Executor中的query方法,可以这样子定义插件:

此外我们还需要将插件配置到sqlMapConfig.xml 中:

1
2
3
4
<plugins>
<plugin interceptor="com.lagou.plugin.ExamplePlugin">
</plugin>
</plugins>

如此,我们就将mybatis的插件配置完成。mybatis在启动时可以加载插件,并保存实例到相关对象(InterceptorChain,拦截器链)中。待准备工作做完后,Mybatis处于就绪状态。我们在执行SQL时,需要先通过DefaultSqlSessionFactory创建SqlSession。Executor实例会在创建sqlSession的过程中被创建,Executor实例创建完毕后,Mybatis会通过JDK动态代理为实例生成代理类。这样,插件逻辑即在Executor相关方法被调用前执行。

自定义插件

插件接口介绍

Mybatis插件接口的核心类是Interceptor,里面有三个主要的方法:

interceptor方法——插件的核心方法

plugin方法——生成target的代理对象

setProperties方法——传递插件所需的参数

自定义插件

下面我们来自定义一个插件,来感受下mybatis插件的使用方法。

我们拦截StatementHandler接口中的prepare方法,StatementHandler接口如下:

第一步,定义拦截器如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.lagou;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;


@Intercepts({ //此处可以定义多个@Signature对多个地方进行拦截,都用这个拦截器
@Signature(type = StatementHandler.class, //指拦截哪个接口
method = "prepare", //拦截接口内的哪个方法名
args = {Connection.class,Integer.class}) //这是拦截方法的入参,按顺序写到这,不要多也不要少,
// 如果方法重载,可要通过方法名和入参来确定唯一的
})
public class MyPlugin implements Interceptor {

//每次执行操作的时候,都会进行这个拦截器的方法内的
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行了增强。。。");
return invocation.proceed(); //执行原方法
}

//主要是把拦截器生成一个代理放到代理器链中
@Override
public Object plugin(Object o) {
// System.out.println("将要包装的目标对象:"+o);
return Plugin.wrap(o,this);
}

/**
* 获取配置文件的属性
* 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的初始化参数:"+properties);
}
}

我们简单的进行了一些打印输出,并没有复杂的代码,只是为了测试。

第二步,配置sqlMapConfig.xml:

1
2
3
4
5
6
 <plugins>
<plugin interceptor="com.lagou.MyPlugin">
<!-- 配置参数-->
<property name="name" value="bob"/>
</plugin>
</plugins>

需要注意配置的顺序,在标签中和其他标签有顺序的要求,需要注意。

至此,我们mybatis的插件已经完成。

测试

运行我们之前的测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 测试一级缓存
*/
@Test
public void firstLevelCache(){
//第一次查询
User user1 = userDao.selectUserByUserId(1);
//更新操作
User user=new User();
user.setId(2);
user.setUsername("333哈哈");
userDao.updateUserByUserId(user);
//第二次查询
User user2 = userDao.selectUserByUserId(1);
System.out.println(user1==user2);
}

控制台打印结果输出如下:

可以看出只执行一次setProperties方法来设置参数,在每次查询之前都会调用intercept方法来进行增强。

源码

mybatis缓存demo

总结

我们学习了mybatis插件的基本原理,剖析了插件的执行步骤,然后自定义了一个mybatis的插件,对如何自定义mybatis插件有了实际的认识。