0%

(九)MyBatis学习笔记-插件开发

1. 插件原理

MyBatis在四大对象的创建过程中,都会有插件进行介入。 插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果 。MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。

默认情况下,MyBatis 允许使用插件来拦截的对象和方法包括如下内容:
在这里插入图片描述
之前在讲MaBatis的运行原理的时候,讲到了使用拦截器链来包装生成四大对象,以执行器Executor为例。其创建代码如下:
在这里插入图片描述
其中pluginAll方法的代码如下:
在这里插入图片描述
该方法获取所有的拦截器Interceptor,并调用拦截器的plugin方法包装并返回目标对象target。

而对于插件,我们需要实现Interceptor接口。我们可以使用插件为目标对象创建一个代理对象,也就是为四大对象创建代理对象,而代理对象就可以拦截到四大对象的每一个执行。我们在执行前后或者基于该执行自定义我们自己的执行过程。

2. 插件编写与运行

2.1 步骤

  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
    @Intercepts({
    @Signature(type=StatementHandler.class, method="parameterize",args=java.sql.Statement.class)
    })
    public class MyFirstPlugin implements Interceptor {

    /**
    * 拦截目标对象和目标方法的执行
    */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("intercept方法拦截的目标方法:" + invocation.getMethod());
    //调用proceed即执行目标方法,方法的内容为:method.invoke(target, args);
    Object proceed = invocation.proceed();
    //返回执行后的返回值
    return proceed;
    }

    /**
    * 该方法用来包装目标对象,这里需要为目标对象创建一个代理对象。
    */
    @Override
    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属性设置进来
    */
    @Override
    public void setProperties(Properties properties) {
    System.out.println("插件配置的信息:" + properties);
    }

    }
  2. 使用@Interceptors注解完成插件签名,示例如下:
    在这里插入图片描述
    其中Intercepters标签类中定义如下,其声明了该标签需要实现的内容,也就是Signature注解。
    在这里插入图片描述
    Signature注解定义了如下内容,其中type是我们要拦截的四大对象的Class对象,method则是该对象中要拦截的方法,args则是该方法中的参数的Class对象。
    在这里插入图片描述

  3. 将写好的插件注册到全局配置文件中。
    在这里插入图片描述

  4. 运行结果
    在这里插入图片描述

    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin...intercept方法拦截的目标方法:" + invocation.getMethod());
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是:"+value);
//修改完sql语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", 3);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}

运行结果:
在这里插入图片描述
从结果可以知道,我们传入的id值是1,但是我们实际使用的id是3去查询3号员工。

------ 本文结束------