MyBatis 插件

MyBatis使用责任链的封装了Mapper的调用过程,在SqlSession四大对象调度的时候我们有机会插入

代码去执行一些特殊要求以满足场景需求,这便是Mybatis 插件技术

 

插件接口

package org.apache.ibatis.plugin;

import java.util.Properties;

/**
 * Mybatis 模板模式
 * @author Clinton Begin
 */
public interface Interceptor {

  /**
   * 直接覆盖要拦截对象原有的方法,核心方法
   * @param invocation 通过它可以反射调度原来对象的方法
   * @return
   * @throws Throwable
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 给被拦截对象生成一个代理对象,并返回它
   * @param target 被拦截对象
   * @return
   */
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  /**
   * 插件初始化时调用,然后插件对象会被存到配置中
   * @param properties 允许在 plugin 元素中配置所需的参数
   */
  default void setProperties(Properties properties) {
    // NOP
  }

}

插件的初始化

插件的初始化是在MyBatis初始化的时候完成的,在 XMLConfigBuilder 类中查看 pluginElement(XNode parent) 方法向上查找可知

在MyBatis上下文初始化过程中,就开始读入插件配置信息,同时使用反射技术生成插件实例,然后调用插件方法中的setProperties方法

设置我们配置的参数,然后将插件实例保存到配置中,以便读取和使用

 

插件的代理和反射设计

插件用的是责任链模式(一个对象在多个角色中传递,处在传递链上的任何角色都有处理它的机会)

MyBatis的责任链是由inteceptorChain去定义的,这个可以在Configuration类的 newExecutor方法中看到

executor = (Executor) interceptorChain.pluginAll(executor);

pluginAll方法是生成代理对象的方法, 有多少个拦截器就生成多少个代理对象,这样每个插件都可以拦截到真实的对象

MyBatis提供实现了InvocationHandler接口的Plugin类,方便编写代理类

 

 

wrap方法为我们生成这个对象的动态代理对象

如果使用这个类为插件生成代理对象,name代理对象调用方法的时候就会进入到invoke方法中,如果存在签名的拦截方法,插件的intercept方法就会被我们在这里调用,然后返回结果,

如果不存签名方法,那么将直接使用反射调度我们要执行的方法

 

工具类 - MetaObject

编写插件时常用到的工具类,可以读取或修改一些重要的属性

MyBatis中大量使用了这个类镜像包装,包括四大对象,可以通过MetaObject来操作被包装的对象

forObject ,用于包装对象,使用 SystemMetaObject.forObject(Object obj)

getValue, 用于获取对象的属性值,支持OGNL

setValue,设置对象的属性值,支持OGNL

 

插件开发过程

MyBatis插件可以拦截SqlSession中四大对象的任意一个,但是需要注册签名才能够运行插件

签名需要一些规则:

确定需要拦截的对象

  • Executor ,是执行SQL的全过程,参数、组装结果集返回和执行SQL过程都可以拦截
  • StatementHandler,执行SQL的过程,可以重写执行SQL的过程,
  • ParameterHandler,主要拦截执行SQL的参数组装,可以重写组装参数规则
  • ResultSetHandler,拦截执行结果的组装,可以重写组装结果的规则

拦截方法和参数

package com.test.plugin;


import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.sql.Connection;

@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare", // 拦截 StatementHandler 中的 prepare 方法
                args = {Connection.class}) // 拦截 prepare 方法的 Connection 参数
})
public class MyPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return null;
    }
}

 

实现完整拦截

package com.test.plugin;

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

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

@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare", // 拦截 StatementHandler 中的 prepare 方法
                args = {Connection.class}) // 拦截 prepare 方法的 Connection 参数
})
public class MyPlugin implements Interceptor {

    /**
     * 代替拦截对象方法的内容
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("执行前...");
        Object obj = invocation.proceed();
        System.out.println("执行后...");
        return obj;
    }


    /**
     * 生成代理对象,除非自定义,否则不用重写
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    /**
     * 读取Plugin元素上配置的属性
     * 例如:
     * <Plugin>
     *  <property name="aname" value="unknown" />
     * </Plugin>
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println(properties.getProperty("aname"));
    }
}

 

配置插件

在配置文件中增加Plugin元素

<plugins>
  <plugin interceptor="com.test.plugin.MyPlugin">
    <property name="aname" value="unknown"/>
  </plugin>
</plugins>

 

总结

  • 尽量少使用插件,因为这会修改MyBatis底层设计
  • 插件底层是责任链模式,通过反射方法运行,性能不高,尽量减少代理处理的代码,从而提高性能
  • 编写插件需要了解SqlSession四大对象的运行原理

 

posted @ 2020-11-13 21:30  半雨微凉  阅读(141)  评论(0)    收藏  举报