1. MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
  2. MyBatis允许在已映射语句执行过程中的某一点进行拦截调用
  3. 默认情况下,MyBatis允许使用插件来拦截的方法调用包括
    1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    2. ParameterHandler (getParameterObject, setParameters)
    3. ResultSetHandler (handleResultSets, handleOutputParameters)
    4. StatementHandler (prepare, parameterize, batch, update, query)

插件开发

  1. 编写插件实现Interceptor接口,并使用@Intercepts 注解完成插件签名。

    1. @Intercepts({
    2. @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
    3. })
    4. public class MyFirstPlugin implements Interceptor{}
  2. 在全局配置文件中注册插件

    <!--这个步骤是在myBatis-config.xml中完成的-->
    <!--plugins:注册插件-->
    <plugins>
     <plugin interceptor="com.zh.mybatis.dao.MyFirstPlugin">
         <!--这儿传递的参数会在setProperties方法中处理-->
         <property name="username" value="root"/>
         <property name="password" value="root"/>
     </plugin>
    </plugins>
    

插件原理

  1. 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理。
  2. 多个插件依次生成目标的代理对象,层层包裹,先声明的的先包裹,形成代理连。
  3. 目标方法执行时依次从外到内执行插件的intercept方法。
  4. image-20201002002658535.png
  5. 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值。

Interceptor接口

  1. Intercept:拦截目标方法执行
  2. plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法。
  3. setProperties:注入插件配置是设置的属性。
  4. image-20201002003232124.png

常用代码:

从代理连中分离真实被代理对象

// 1. 分离代理对象。由于会形成多次代理,所以需要通过一个while循环分离出最终被代理对象,从而方便提取信息。
MetaObject metaObject = SystemMetaObject.forObject(target);
while(metaObject.hasGetter("h")) {
    Object h = metaObject.getValue("h");
    metaObject = SystemMetaObject.forObject(h);
}
// 2. 获取到代理对象中包含的被代理的真实对象
Object obj = metaObject.getValue("target");
// 3. 获取被代理对象的MetaObject方便进行信息提取
MetaObject forObject = SystemMetaObject.forObject(obj);
// 经典代码
package com.zh.mybatis.dao;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.util.Properties;

/**
 * 插件签名:
 *      告诉MyBatis当前插件用来拦截哪个对象的哪个方法
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {

    /**
     * intercept
     * 拦截目标方法的执行
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MyFirstPlugin.intercept: " + invocation.getMethod());

        // 动态改变一下sql运行的参数:以前1号员工,实际从数据库查询4号员工的信息
        Object target = invocation.getTarget();
        System.out.println("当前拦截到的对象:" + invocation.getTarget());
        // 拿到StatementHandler ==> ParameterHandler ==> parameterObject
        // 拿到target的元数据
        MetaObject metaObject = SystemMetaObject.forObject(target);
        Object value = metaObject.getValue("parameterHandler.parameterObject");
        System.out.println("sql语句用的参数时:" + value);
        metaObject.setValue("parameterHandler.parameterObject", 5);

        // 执行目标方法
        Object proceed = invocation.proceed();

        // 返回执行后的返回值
        return proceed;
    }

    /**
     * plugin
     * 包装目标对象:包装:为目标对象创建一个代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("MyFirstPlugin.plugin: " + target);

        // 我们可以借助Plugin的warp的方法来使用当前的Interceptor包装目标对象
        Object wrap = Plugin.wrap(target, this);
        // 返回为当前target创建动态代理
        return wrap;
    }

    /**
     * setProperties
     * 将插件注册时的property属性设置进来
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("MyFirstPlugin.setProperties插件配置的信息:" + properties);
    }
}