Mybaties插件原理

本文主要解析Mybaties插件基本原理,所谓的插件就是Mybaties提供的Interceptor拦截器,用于SQL执行时动态对“执行方法”、“参数”、“返回值”或“SQL语句”的拦截处理。在业务很多场景都会用到,如分页和sql操作同步等场景。

1. MyBatis拦截原理

MyBatis插件可以拦截四个接口的方法:ExecutorParameterHandlerStatementHandlerResultSetHandler。顾名思义,这四个接口都有名称相近的处理操作,前述四个接口分别表示:执行器拦截、参数拦截、结果集处理和SQL构建。以Executor为例,拦截Executorint update(MappedStatement ms, Object parameter) throws SQLException方法,也就是业务在执行insert、update或者delete操作前后都会被该方法拦截。将操作拦截下来,判断结果是否正确或者满足,在自定义操作。

拦截器接口代码

package org.apache.ibatis.plugin;

import java.util.Properties;

/**
 * @author Clinton Begin
 */
public interface Interceptor {
  
  // 拦截处理放在该方法中
  Object intercept(Invocation invocation) throws Throwable;

  // 代理生成实现类对象,说白了为了执行实现类对象的intercept方法
  Object plugin(Object target);

  void setProperties(Properties properties);
}

Interceptor.plugin(Object target)方法被InterceptorChain.pluginAll(Object target)方法调用,源码

package org.apache.ibatis.plugin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

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

  // 会被Configuration配置类调用
  // 在进行增删改查时,会遍历所有拦截器,并执行其plugin方法
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  // 会被Configuration.addInterceptor(Interceptor interceptor)调用
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

MyBatis提供了@Intercepts@Signature拦截器相关的注解。增删改查等数据库操作会进入拦截器Interceptor,拦截时会根据拦截器上的这两个注解值来确定拦截与否。比如@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})表示该拦截器将处理Executor组件且参数是(MappedStatement.class, Object.class)的update方法。具体的判断逻辑是在Plugin.wrap(target, this)中。

Mybaties的Plugin类源码

// 该类实现了InvocationHandler接口,用于JDK动态代理
// 这样做的好处是在动态代理调用invoke方法时,会直接使用生成代理对象时缓存的方法如target、interceptor和signatureMap。
public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  // 生成代理对象
  public static Object wrap(Object target, Interceptor interceptor) {
    // @Signature注解的type属性为key,method属性集合为value的Map对象
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 获取被代理对象实现的接口,但接口类型必须在signatureMap的key中
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // JDK动态代理是接口的代理,实现接口不能为空
    if (interfaces.length > 0) {
      // 生成代理对象
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  // 代理Interceptor.intercept(Invocation invocation)方法
  @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);
    }
  }

  // 该方法主要用于返回参数是@Signature注解的type属性为key,method属性集合为value的Map对象
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // 拿到拦截器上Intercepts注解,不存在时会抛异常
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    Signature[] sigs = interceptsAnnotation.value();
    // 返回的对象
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      // sig.type()即是@Signature注解的type属性
      Set<Method> methods = signatureMap.get(sig.type());
      // signatureMap没有值时,新建一个Set存入
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        // 通过函数名和参数类型确定具体的Method
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

  // 获取被代理对象实现的接口,但接口类型必须在signatureMap的key中
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    // 含父类,父类的父类...,直到遍历完父类实现接口
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        // 要包含在signatureMap的key中
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      // 父类
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

在调用Mybaties配置类Configuration的newExecutornewParameterHandlernewResultSetHandlernewStatementHandler方法时都会调用所有拦截器。

2. 一个拦截器示例

package com.mingo.es.sync.mybaties.plugin;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.sql.Statement;
import java.util.Arrays;
import java.util.Properties;

/**
 * MyBaties拦截测试。这里只拦截处理:
 * Executor.update(MappedStatement ms, Object parameter) 和
 * StatementHandler.update(Statement statement)
 *
 * update表示insert、update和delete
 * 
 * @author Doflamingo
 */
@Component
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
})
public class TestInterceptor implements Interceptor {

    /**
     * 拦截处理
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object res = invocation.proceed();

        System.out.println("拦截处理的接口和方法分别是:" +
                Arrays.toString(invocation.getTarget().getClass().getInterfaces()) + ", " +
                invocation.getMethod().getName()
        );
        return res;
    }

    /**
     * 生成代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        // 代理对象,用于触发intercept()
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

StatementHandler.update(Statement statement)方法表示将 insert、update、delete 操作推送到数据库。写一个测试用例,将一条数据插入数据库表中。执行结果如下

拦截处理的接口和方法分别是:[interface org.apache.ibatis.executor.statement.StatementHandler], update
拦截处理的接口和方法分别是:[interface org.apache.ibatis.executor.Executor], update

 

posted @ 2020-09-19 20:15  别名  阅读(234)  评论(0编辑  收藏  举报