1. 插件原理
MyBatis在四大对象的创建过程中,都会有插件进行介入。 插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果 。MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
默认情况下,MyBatis 允许使用插件来拦截的对象和方法包括如下内容:
之前在讲MaBatis的运行原理的时候,讲到了使用拦截器链来包装生成四大对象,以执行器Executor为例。其创建代码如下:
其中pluginAll方法的代码如下:
该方法获取所有的拦截器Interceptor,并调用拦截器的plugin方法包装并返回目标对象target。
而对于插件,我们需要实现Interceptor接口。我们可以使用插件为目标对象创建一个代理对象,也就是为四大对象创建代理对象,而代理对象就可以拦截到四大对象的每一个执行。我们在执行前后或者基于该执行自定义我们自己的执行过程。
2. 插件编写与运行
2.1 步骤
先编写Interceptor的实现类,即插件类
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({
"parameterize",args=java.sql.Statement.class) (type=StatementHandler.class, method=
})
public class MyFirstPlugin implements Interceptor {
/**
* 拦截目标对象和目标方法的执行
*/
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("intercept方法拦截的目标方法:" + invocation.getMethod());
//调用proceed即执行目标方法,方法的内容为:method.invoke(target, args);
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* 该方法用来包装目标对象,这里需要为目标对象创建一个代理对象。
*/
public Object plugin(Object target) {
System.out.println("plugin将要包装的对象:" + target);
//借助Plugin的wrap方法来使用当前Intercepter包装目标对象
//相当于调用Proxy.newProxyInstance方法
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理对象
return wrap;
}
/**
* 将插件注册时的property属性设置进来
*/
public void setProperties(Properties properties) {
System.out.println("插件配置的信息:" + properties);
}
}使用@Interceptors注解完成插件签名,示例如下:
其中Intercepters标签类中定义如下,其声明了该标签需要实现的内容,也就是Signature注解。
Signature注解定义了如下内容,其中type是我们要拦截的四大对象的Class对象,method则是该对象中要拦截的方法,args则是该方法中的参数的Class对象。将写好的插件注册到全局配置文件中。
运行结果
2.2 执行原理
当四大对象通过拦截器链的pluginAll方法时,因为我们编写了实现拦截器接口的插件类,这时运行之后就会调用MyFirstPlugin类的plugin方法。如下图示:
执行MyFirstPlugin类的plugin方法后,会调用Plugin类的wrap方法。
在wrap方法中,会通过if语句来判断signatureMap内容中是否有我们要拦截的对象和方法(这里抽取到interfaces中),如果没有则直接返回target,有则创建代理对象。
插件产生目标对象的如下代理对象之后,会返回给MyBatis使用。
之后调用Statement的预编译时,会通过代理对象调用我们插件类MyFirstPlugin中的拦截方法intercept,输出我们自定义的语句之后,调用proceed方法直接放行。
输出结果大致含义:
3. 多插件运行
在原有的基础在创建一个插件MySecondPlugin,并在全局配置文件进行注册,插件类编写与MyFirstPlugin基本一样,这里不再赘述。
其运行结果如下:
需要注意的是,创建动态代理的时候,是按照插件配置顺序创建层层代理对象。
执行目标方法的之后,按照逆向顺序执行。如下图示:
另外,多个插件就会产生多层代理,也就是在第一个产生的代理对象中在基础上产生一层代理,大致结构如下:
4. 简单应用
需求:使用插件将本来查询1号的员工的查询语句在执行过程中改成查询3号员工。
修改MyFirstPlugin的拦截方法,内容如下:
1 |
|
运行结果:
从结果可以知道,我们传入的id值是1,但是我们实际使用的id是3去查询3号员工。