MyBatis-07-外掛原理

YangDanMua發表於2024-04-16

MyBatis 外掛

MyBatis 的外掛實際上就是 Interceptor,攔截器,攔截並重寫 SQL
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);
}

相關文章