MyBatis-07-插件原理

MyBatis 插件

MyBatis 的插件实际上就是 Interceptor,拦截器,拦截并重写 SQL
为什么叫插件而不叫拦截器,因为在包 plugin 里,( ﹁ ﹁ ) ~→
image

Interceptor: 拦截器
InterceptorChain: 拦截器链
Invocation: 封装 Object.method(args),反射调用
Plugin: JDK 代理处理器 InvocationHandler,封装一个 Interceptor 及其拦截的方法,及 拦截对象 + 拦截方法 + Interceptor,就可以 invoke 前先进行拦截处理了

Interceptor

拦截器,只能拦截 MyBatis 中的四个对象

  • ParameterHandler
  • ResultSetHandler
  • StatementHandler
  • Executor
    于是当这几个对象被 new 时,会代理他们,在代理逻辑中对他进行拦截
public interface Interceptor {
  // 拦截器逻辑
  Object intercept(Invocation invocation) throws Throwable;
  // 封装为插件, 当 target 要被拦截时, 取出所有拦截器, 均调用他们的 plugin, 最终得到一个封装了要拦截的对象和所有拦截器实例的对象
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

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

InterceptorChain

实际就是多个 Interceptor 的更进一步封装,拦截器链
查看 pluginAll 的引用,可以看见就是 MyBatis 的四大对象的 new 的地方使用到了

public class InterceptorChain {
  // 持有所有拦截器
  private final List<Interceptor> interceptors = new ArrayList<>();
  // 调用所有拦截器的 plugin 方法, 每调用一次生成一个代理对象, 后面的拦截器在前一个代理对象的基础上继续生成代理对象
  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);
  }

}

Intercepts、Signature

声明拦截器及拦截的方法
如下案例

@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }))
public static class SwitchCatalogInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    Connection con = (Connection) args[0];
    con.setSchema(SchemaHolder.get());
    return invocation.proceed();
  }
}

Invocation

配合封装拦截器的,从构造函数可以看出仅支持 Executor、ParameterHandler、ResultSetHandler、StatementHandler 的封装

public class Invocation {

  private static final List<Class<?>> targetClasses = Arrays.asList(Executor.class, ParameterHandler.class,
      ResultSetHandler.class, StatementHandler.class);
  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    if (!targetClasses.contains(method.getDeclaringClass())) {
      throw new IllegalArgumentException("Method '" + method + "' is not supported as a plugin target.");
    }
    this.target = target;
    this.method = method;
    this.args = args;
  }

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

}

Plugin

实现插件/拦截的逻辑,也是为什么叫做插件的原因

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) {
    // 获取拦截器要拦截的所有方法(类 --> 方法列表)
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
	// 要被代理的实例的所有接口, 用于创建 JDK 代理
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
	// 有接口才会代理, 本身 MyBatis 的四大对象都是接口
    if (interfaces.length > 0) {
	  // Plugin 是 InvocationHandler, 实现了代理逻辑
      return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @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);
    }
  }

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // Intercepts 注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
	// 为空, 这种应该是注入了 Interceptor 类型的但是没有使用注解
    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<>();
    for (Signature sig : sigs) {
      Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
      try {
	    // 获取方法实例
        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;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
	    // 这里只获取了要拦截的接口列表
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[0]);
  }

}

总结

image

XMLConfigBuilder#pluginsElement: 解析 Interceptor
  - Configuration#addInterceptor: 添加到 Configuration 中
    - InterceptorChain#addInterceptor: Configuration 持有 InterceptorChain 实例, 添加到 InterceptorChain 中

Configuration new 四个对象时都调用了拦截器链的代理方法

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
    BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
      parameterObject, boundSql);
  // 调用
  return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,
    ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,
      resultHandler, boundSql, rowBounds);
  // 调用
  return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
    Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
      rowBounds, resultHandler, boundSql);
  // 调用
  return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : 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);
  }
  // 调用
  return (Executor) interceptorChain.pluginAll(executor);
}

分页的拦截

PageHelper 和 MP 的分页逻辑都是拦截 Executor.query 方法,根据原 SQL 生成 count SQL,然后根据diaect 生成 分页 SQL 方言,最终执行分页查询逻辑

MP 在 MyBatis 的 Interceptor 基础上增加了自己的 InnerInterceptor,提供了更多的类似 before 这种切入点。

当然 MP 的分页属于 MP 的扩展。

!Mybatis四大组件、四大对象、三个组成部分

posted @ 2024-04-16 22:45  YangDanMua  阅读(11)  评论(0编辑  收藏  举报