MyBatis印象閱讀之插件解析

今天我們來講下關(guān)于MyBatis的插件功能。為什么會(huì)有這個(gè)功能呢? 我們結(jié)合我們可能接觸過的知識(shí)點(diǎn)類比可能能更好的理解。

在我看來,MyBatis的插件功能可以和Spring的AOP進(jìn)行比較,主要功能都是為了擴(kuò)展,當(dāng)然功能沒有AOP那么強(qiáng)大,也不需要。它們的相同點(diǎn)是可以在指定位置來進(jìn)行業(yè)務(wù)功能的增強(qiáng)實(shí)現(xiàn),不同點(diǎn)是AOP更加強(qiáng)大豐富。

比較完了,我們直接來進(jìn)入實(shí)例去了解:

1. MyBatis插件實(shí)例

這里我還是以一個(gè)例子來拋磚引玉,我們進(jìn)入到Test中的一個(gè)測(cè)試方法:

  @Test
  void mapPluginShouldInterceptGet() {
    Map map = new HashMap();
    map = (Map) new AlwaysMapPlugin().plugin(map);
    assertEquals("Always", map.get("Anything"));
  }
  
  
    @Intercepts({
      @Signature(type = Map.class, method = "get", args = {Object.class})})
  public static class AlwaysMapPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) {
      return "Always";
    }

    @Override
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }

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

這里有很多我們目前還沒接觸過的,我們一個(gè)個(gè)來分析。

1.1 接口Interceptor解析

public interface Interceptor {

  /**
   * 執(zhí)行插件內(nèi)容
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 封裝
   */
  Object plugin(Object target);

  /**
   * 參數(shù)賦值
   */
  void setProperties(Properties properties);

}

大致熟悉有這樣三個(gè)方法之后,我們來繼續(xù)看他使用的注解有 @Intercepts、 @Signature具體功能我們根據(jù)之前的test方法和這個(gè)類來進(jìn)行猜想一下,之后再來做分析,下面我們繼續(xù)來看我們這唯一調(diào)用了一個(gè)MyBatis方法Plugin.wrap:

  public static Object wrap(Object target, Interceptor interceptor) {
    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;
  }

我們重點(diǎn)來看getSignatureMap方法:

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    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<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(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;
  }

這里就可以看出我們之前 @Intercepts、 @Signature的作用,主要功能就是獲取注解的信息,解析出來我們需要代理插件對(duì)應(yīng)的方法,代碼邏輯也比較清晰.下面一步是獲取對(duì)應(yīng)的接口,我們來看
getAllInterfaces:

  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<?>[interfaces.size()]);
  }

這里的代碼邏輯都比較簡(jiǎn)單,獲取我們class類對(duì)應(yīng)的所有方法,包括他的父類,然后是否匹配我們注解對(duì)應(yīng)的方法,有就添加。最后如果得到的接口不為空,則進(jìn)行包裝代理,代理方式是java的代理,這個(gè)我們不做展開,只要知道我們的Plugin實(shí)現(xiàn)了InvocationHandler接口:

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

整體的流程就是這樣,結(jié)合進(jìn)MyBatis,無非是代理的方法更換。不過重點(diǎn)要記住的是,MyBatis只支持在某幾個(gè)點(diǎn)來進(jìn)行代理,我們來看下官網(wǎng):

MyBatis 允許你在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(diào)用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

2. 今日總結(jié)

我們今天來了解了關(guān)于MyBatis插件的源碼,相對(duì)也比較簡(jiǎn)單。到這里,我們整體的MyBatis內(nèi)容源碼就講的差不多了,收貨還是有的,所以我要繼續(xù)堅(jiān)持下去~~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容