Mybatis学习之插件

Mybatis学习之插件

Plugins

Mybatis中的插件虽然名称叫插件,但实质上是通过动态代理实现的。和我们平时讲的插件概念不一样,但是本质上都是给外部提供接口进行扩展。

MyBatis 允许我们在映射语句执行过程中的某一点进行拦截调用。MyBatis允许我们使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

自定义插件

在Mybatis中自定义插件只需要以下几步:

  1. 创建自定义插件类实现Interceptor接口;
  2. 使用@Intercepts注解:指定插件拦截四大对象中指定对象的指定方法;
  3. 在全局配置文件中注册插件

自定义插件代码如下:

这里使用@Intercepts注解指定了拦截Executor对象的update方法和query方法,由于接口中存在重载方法,所以通过args指定方法的参数来确定是哪一个方法。

@Intercepts({
        @Signature(type = Executor.class,method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class,method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class })})
public class MyFirstPlugin implements Interceptor {


    /**
     * 拦截目标对象的目标方法的执行
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("要拦截的方法: " + invocation.getMethod());
        //得到当前对象
        Object target = invocation.getTarget();
        System.out.println("拦截的对象:" + target);
        MetaObject metaObject = SystemMetaObject.forObject(target);
        Object[] args = invocation.getArgs();

        if (args[0] instanceof MappedStatement) {
            MappedStatement statement = (MappedStatement) args[0];
            System.out.println("=====================statement: " + statement);
            System.out.println("=====================statement id: " + statement.getId());
            System.out.println("=====================statement paraneterMap: " + statement.getParameterMap());
        }

        //执行原来的方法
        Object prObject = invocation.proceed();

        return prObject;
    }

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

    /**
     * 将插件注册时的property属性设置进来
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
        System.out.println("读取到配置文件的参数是:" + properties);
    }
}

将插件注册到全局配置文件中
在plugin标签中可以使用property设置要传递的参数,这里设置的参数,可以在setProperties方法中获取到。

    <plugins>
        <plugin interceptor="com.ruoyi.web.test.MyFirstPlugin">
            <property name="root" value="jjc"/>
            <property name="sdsdfs" value="123456"/>
        </plugin>
    </plugins>

插件原理

以上面的代码为例,在启动的时候,XMLConfigBuilder中的pluginElement方法会解全局配置文件中plugins节点。
Mybatis学习之插件_第1张图片

Mybatis学习之插件_第2张图片

configuration的addInterceptor方法最后会把拦截器加入到拦截器链中;而拦截的时候也会遍历拦截器链中的每一个拦截器,调用拦截器的plugin方法进行拦截。
Mybatis学习之插件_第3张图片

resolveClass方法根据全限定类名解析出Class对象
Mybatis学习之插件_第4张图片
Mybatis会尝试先从类型注册器中获取,如果没有则会使用类加载器根据全限定类型去加载(Resource.classForName中做了这个事情)。
Mybatis学习之插件_第5张图片
到这里为止,Mybatis配置文件中的插件的解析就完成了。

现在我们来看看Mybatis是在什么时候拦截的。

下面截图中的方法是Configuration中的4个方法。这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor、ParameterHandler、ResultSetHandler、StatementHandler;

其中ParameterHandler和ResultSetHandler的创建是在创建StatementHandler(3个可用的实现类CallableStatementHandler、PreparedStatementHandler、SimpleStatementHandler)的时候其构造函数调用的(这3个实现类的构造函数其实都调用了父类BaseStatementHandler的构造函数)。
Mybatis学习之插件_第6张图片
这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,InterceptorChain的pluginAll上面已经说过了,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象

接下来分析一下Plugin.wrap方法的执行。

  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

getSignatureMap方法:首先会拿到拦截器这个类的@Interceptors注解,然后拿到这个注解的属性@Signature注解集合,然后遍历这个集合,遍历的时候拿出@Signature注解的type属性(Class类型),然后根据这个type得到带有method属性和args属性的Method。由于@Interceptors注解的@Signature属性是一个属性,所以最终会返回一个以type为key,value为Set的Map。

getAllInterfaces方法:根据目标实例target(这个target就是之前所说的MyBatis拦截器可以拦截的类,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父类们,返回signatureMap中含有target实现的接口数组。

所以Plugin类的作用就是根据 @Interceptors注解,得到这个注解的属性 @Signature数组,然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。

如果返回了代理类,那么代理类最终会执行Interceptor接口的interceptor方法。
Mybatis学习之插件_第7张图片

参考

  1. 视频课程案例讲解
  2. Mybatis官网。

你可能感兴趣的:(mybatis,学习,java,插件)