Mybatis插件使用和原理

Mybatis插件

一、官方说明

官方文档中在插件章节也进行了说明:

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 【重写SQL】
  • ParameterHandler (getParameterObject, setParameters) 【处理参数】
  • ResultSetHandler (handleResultSets, handleOutputParameters) 【处理结果集】
  • StatementHandler (prepare, parameterize, batch, update, query) 【参数、结果集、sql都可处理】

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

二、官方实例

mybatis还贴心的给了一个例子:

// ExamplePlugin.java
@Intercepts({@Signature(
    type= Executor.class,
    method = "update",
    args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
    private Properties properties = new Properties();
    public Object intercept(Invocation invocation) throws Throwable {
        // implement pre processing if need
        Object returnObject = invocation.proceed();
        // implement post processing if need
        return returnObject;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

在全局配置文件中来进行声明:

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

在property中,还可以来针对插件信息来做一个补充说明机制,添加额外信息可以让插件类使用。

这个地方很有用,mybatis-plus中就利用到了这里的配置信息,比如动态表、逻辑删除等等操作。

三、Configuration创建插件

四大拦截器对象都是由configurataion对象创建出来的,所以configurataion有权来对其做改造。

public StatementHandler newStatementHandler
public Executor newExecutor    
public ResultSetHandler newResultSetHandler
public ParameterHandler newParameterHandler 

1、Mybatis对插件的解析

先将官方给的实例拿下来:

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

直接来到org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement

pluginElement(root.evalNode("plugins"));

具体实现:

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 获取得到插件配置标签
            String interceptor = child.getStringAttribute("interceptor");
            // 获取得到Properties中的配置信息
            Properties properties = child.getChildrenAsProperties();
            // 创建拦截器示例
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            // 回调setProperties放阿飞
            interceptorInstance.setProperties(properties);
            // 添加拦截器
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

首先拿到插件的全限定类名,然后获取得到子属性,也就是property中的name和value

public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
            properties.setProperty(name, value);
        }
    }
    return properties;
}

将属性和值保存到properties中。然后直接创建插件对象,并将属性设置到实例中去。

然后将插件加入到configuration对象中的拦截器中,最终保存到InterceptorChain类中的

private final List<Interceptor> interceptors = new ArrayList<>();

保存到结合中来。

2、使用插件的地方

以Executor为例:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

注意下面这行代码:

将当前的执行器传进去,然后进行判断

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // 从这里可以看到代理逻辑
      target = interceptor.plugin(target);
    }
    return target;
  }

然后来到接口中:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;
  // 可以手动重写
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

一般来说,plugin会来判断是哪种类型,然后进行代理。下面看下代理逻辑:

  public static Object wrap(Object target, Interceptor interceptor) {
    // 解析@Intercepts和@Signature,哪些方法需要被拦截
    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;
  }

看到了动态代理,那么就意味着,有对应的Invoke方法:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
         // 调用拦截方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

来到:

public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<>();

    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}

首先循环遍历插件集合,然后调用插件方法

然后会执行到:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

所以重点看下这里的Plugin.wrap方法做了什么事情:

点进去看了之后,发现很简单,动态代理方式。

  public static Object wrap(Object target, Interceptor interceptor) {
    // 获得interceptor配置的@Signature的type
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 当前代理类型
    Class<?> type = target.getClass();
    // 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

使用了动态代理来将四大插件对象给替换成了动态代理对象,而在执行执行方法的时候,会来执行Plugin中的invoke方法

看下实现过程:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 获取得到类中的方法
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        // 如果包含,那么来执行对应的intercept逻辑
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}

然后就会执行到我们自定义中的分页插件中的intercept方法

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

对于setProperties来说,子类中可以自定义属性来进行保存。而对于intercept则是用户根据自己的需求来进行定义的。

3、简化步骤

在实现intercept方法的时候,如果自定义逻辑执行完成,那么该让程序继续向下执行的时候,不需要我们再来进行

method.invoke(target,args);

我们可以直接调用intercept方法中的参数Invocation,因为在Invocation中封装了一个方法:

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

直接来进行调用即可

四、SpringBoot配置

配置插件方式一:

@Configuration
public class MybatisConfig {
    // 注册插件方式1
    @Bean
    public EditPlugin myPlugin() {
        // 注册成bean就意味着可以从容器中获取得到bean进行操作
        return new EditPlugin();
    }
}

配置插件方式二:

@Configuration
public class MybatisConfig {
//    // 注册插件方式2
//    @Bean
//    public ConfigurationCustomizer configurationCustomizer() {
//        return new ConfigurationCustomizer() {
//            @Override
//            public void customize(org.apache.ibatis.session.Configuration configuration) {
//                //插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
//                EditPlugin myPlugin = new EditPlugin();
//                configuration.addInterceptor(myPlugin);
//            }
//        };
//    }

    // 注册插件方式2
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            //插件拦截链采用了责任链模式,执行顺序和加入连接链的顺序有关
            EditPlugin myPlugin = new EditPlugin();
            configuration.addInterceptor(myPlugin);
        };
    }
}

五、使用场景

1、删除表缓存;

2、分表查:表1查不到;从表二查询;

3、动态插表和查表;

posted @ 2022-09-06 00:01  雩娄的木子  阅读(71)  评论(0编辑  收藏  举报