【Mybatis】【插件】Mybatis源码解析-插件机制

1  前言

这节我们来看看插件,插件是来干啥的呢?一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的 好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行 拓展,使其能够更好的工作。以 MyBatis 为例,我们可基于 MyBatis 插件机制实现分页、 分表,监控等功能。那么我们这节就来看看 Mybatis 里的插件。

2  插件类型

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

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

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

3  插件的简单使用

我们简单来个插件的示例:

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

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("===================my");
    return invocation.proceed();
  }
}
<plugins>
  <plugin interceptor="org.apache.ibatis.test.plugin.MyInterceptor"></plugin>
</plugins>

这样 MyBatis 在启动时可以加载插件, 并保存插件实例到相关对象(InterceptorChain,拦截器链)中。待准备工作做完后,MyBatis 处于就绪状态。我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创 建 SqlSession 。Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕 后,MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相 关方法被调用前执行。以上就是 MyBatis 插件机制的基本原理。接下来,我们来看一下原 理背后对应的源码是怎样的。

4  源码分析

4.1  入场时机

本节,我将以 Executor 为例,分析 MyBatis 是如何为 Executor 实例植入插件逻辑的。 Executor 实例是在开启 SqlSession 时被创建的,因此,下面我们从源头进行分析。先来看 一下 SqlSession 开启的过程:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    ...
    // 创建我们的执行器 进入这里创建执行器
    final Executor executor = configuration.newExecutor(tx, execType);
    // 创建 SqlSession 对象
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

Executor 的创建过程封装在 Configuration 中,我们跟进去:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  ...
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  // 插件
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

如上,newExecutor 方法在创建好 Executor 实例后,紧接着通过拦截器链 interceptorChain 为 Executor 实例植入代理逻辑。那下面我们看一下 InterceptorChain 的代码是怎样的。

public class InterceptorChain {

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

  public Object pluginAll(Object target) {
    // 遍历我们的插件拦截器
    for (Interceptor interceptor : interceptors) {
      // 调用拦截器的 plugin 方法进行插件的创建 其实就是会创建代理类
      target = interceptor.plugin(target);
    }
    return target;
  }

  // 添加插件
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  // 获取插件列表
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

以上是 InterceptorChain 的全部代码,比较简单。它的 pluginAll 方法会调用具体插件的 plugin 方法植入相应的插件逻辑。如果有多个插件,则会多次调用 plugin 方法,最终生成一 个层层嵌套的代理类。那我们就来看下 plugin方法是如何创建代理的。

4.2  plugin 决定是否创建代理

plugin 方法是由具体的插件类实现,不过该方法代码一般比较固定,我们看一下:

// Interceptor 默认实现
default Object plugin(Object target) {
  // 调用 wrap 方法进行包装
  return Plugin.wrap(target, this);
}
/**
 * Plugin 生成代理
 * 这个代理可能会嵌套生成,越后嵌套越先执行奥
 * @param target 目标对象
 * @param interceptor 插件拦截
 * @return 代理对象或者目标对象
 */
public static Object wrap(Object target, Interceptor interceptor) {
  // 解析插件拦截器的信息
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  // 当前被拦截对象的类型
  Class<?> type = target.getClass();
  /**
   * 判断当前的被拦截对象的类型是否存在拦截
   * 比如当前的对象是 statementHandler的 而当前的拦截器是配置的 Executor -> [xxx]
   * 那么当前方法就无需这个插件
   */
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  // 当发现需要插件增强的话,创建当前对象的代理类 jdk方式的
  if (interfaces.length > 0) {
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        // 注意这个这是我们的增强 也就是拦截会执行 Plugin 中的 invoke
        new Plugin(target, interceptor, signatureMap));
  }
  // 不需要的话 直接返回当前对象
  return target;
}

如上,plugin 方法在内部调用了 Plugin 类的 wrap 方法,用于为目标对象生成代理。Plugin 类实现了InvocationHandler接口,因此它可以作为参数传给 Proxy 的 newProxyInstance方法。

我们再来简单看下 getSignatureMap 方法就是解析我们的插件类获取信息的,我们看下:

/**
 * 我们来拿一个例子给你解析
 * @Intercepts({@Signature(
 *   type= Executor.class,
 *   method = "update",
 *   args = {MappedStatement.class, Object.class})})
 *   那么返回的就是 Executor -> [update方法]
 * @param interceptor 就是我们的插件实现类
 * @return 返回类型和方法集合的映射
 */
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  // 获取到我们的 @Intercepts 注解信息
  Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  // 没注解的话直接报错
  if (interceptsAnnotation == null) {
    throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  }
  // 获取到 @Signature 数组
  Signature[] sigs = interceptsAnnotation.value();
  /**
   * 用于保存结果 相同类的话会放进同一个 key 中
   * key 哪种插件类型  value 就是拦截的方法列表
   */
  Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
  for (Signature sig : sigs) {
    // 初始化 key 的集合
    Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
    try {
      // 根据拦截类型 type 以及参数列表获取到对应的拦截方法
      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;
}

4.3  插件执行逻辑

Plugin 实现了 InvocationHandler 接口,因此它的 invoke 方法会拦截所有的方法调用。 invoke 方法会对所拦截的方法进行检测,以决定是否执行插件逻辑。该方法的逻辑如下:

// Plugin 当代理对象执行方法的时候,会代理执行到此
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    /**
     * 获取当前要执行的方法来源于哪个类并获取是否存在方法增强
     * 比如当前要执行的是 Executor 的 query方法 而当前拦截器的类型方法映射为 Executor -> [update]
     * 那么 query 就不在增强范围内,所以就会直接执行目标方法
     */
    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);
  }
}

invoke 方法的代码比较少,逻辑不难理解。首先,invoke 方法会检测被拦截方法是否配 置在插件的 @Signature 注解中,若是,则执行插件逻辑,否则执行被拦截方法。插件逻辑 封装在 intercept 中,该方法的参数类型为 Invocation。Invocation 主要用于存储目标类,方法 以及方法参数列表。下面简单看一下该类的定义。

public class Invocation {
  // 被代理对象
  private final Object target;
  // 当前拦截的方法
  private final Method method;
  // 方法参数
  private final Object[] args;
  public Invocation(Object target, Method method, Object[] args) {
    // 初始化
    this.target = target;
    this.method = method;
    this.args = args;
  }
  public Object getTarget() {
    return target;
  }
  public Method getMethod() {
    return method;
  }
  public Object[] getArgs() {
    return args;
  }
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    // 下一个拦截器执行或者目标方法执行
    return method.invoke(target, args);
  }
}

关于插件的机制就到这里了,他的代理记住是一个套一个的逐层执行的哈。

5  小结

这节我们大致看了一下插件的入场以及插件的实现机制,有理解不对的地方欢迎指正哈。

posted @ 2023-03-09 20:33  酷酷-  阅读(117)  评论(0编辑  收藏  举报