天天看點

mybatis 插件的原理-責任鍊和動态代理的展現

@

目錄

  • 1 攔截哪些方法
  • 2 如何代理
  • 3 代理對象
  • 4 責任鍊設計模式

如果沒有自定義過攔截器, 可以看我前面的文章。如果不知道 JDK 動态代理怎麼使用的, 可以看我這文章。 責任鍊設計模式了解起來很簡單, 網上找個例子看看即可。

mybatis

插件的原理使用的是動态代理和責任鍊來實作的。

在前面說過, 可以通過注解

Intecepts

Signature

來進行指定攔截哪些方法。 然而, 并不是說所有的方法都可以攔截的。

mybatis 攔截器所攔截的方法, 有如下類型:

1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)
           

為什麼說可以攔截的方法是這些呢?

mybatis

中, 以上幾個類是

SqlSession

的四大對象。

SqlSession

通過這些對象實作對資料庫的操作, 結果的處理。 是以, 從流程上來說, 是攔截這幾個對象中的方法是有非常重要的作用的。

mybatis 插件的原理-責任鍊和動态代理的展現

而在源碼上的展現呢, 就在

Configuration

類中, 這個類的重要性不做過多的闡述, 可以看看前面的文章。

在總 xml 解析成

Configuration

過程中, 需要 new 出以上的幾個類。而以上的幾個類在後面都會調用

interceptorChain#pluginAll

方法。

public class InterceptorChain {

  /**
   * 攔截器清單
   */
  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  /**
   * 添加攔截器
   *
   * @param interceptor
   */
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  /**
   * 擷取攔截器清單
   *
   * @return
   */
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

           

可以看到, 其會将調用所有攔截器的

plugin

方法, 層層代理之後傳回最終的代理對象。 要注意這裡的層層代理。

如果有 A、B、C 三個攔截器(簽名相同), 則在此時, 會被層層封裝。 最後執行的時候, 是 A>B>C> target.proceed() >C>B>A.

InterceptorChain

會調用每個攔截器中的

plugin

方法。該方法是會傳回相應的代理對象的。

/**
 * 攔截器接口
 *
 * @author Clinton Begin
 */
public interface Interceptor {

  /**
   * 執行攔截邏輯的方法
   *
   * @param invocation 調用資訊
   * @return 調用結果
   * @throws Throwable 異常
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 代理
   *
   * @param target
   * @return
   */
  Object plugin(Object target);

  /**
   * 根據配置來初始化 Interceptor 方法
   * @param properties
   */
  void setProperties(Properties properties);

}

           

其中的

plugin

是需要我們來實作的。 而 mybatis 也給我們提供了很友善的方法。

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

           

我們在重寫 plugin 方法時, 隻需要調用上面這個方法即可。 其會傳回

Plugin

這個類的一個對象。

public class Plugin implements InvocationHandler
           
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 擷取方法所在類中, 可以被攔截的所有的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 如果需要被攔截, 則調用 interceptor.intercept
      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);
    }
  }
           

由于 JDK 的動态代理是接口級别的。 是以, 其代理了類的所有接口的方法。 然而并不是所有的方法都是需要被代理的, 是以, 在方法中通過注解中的簽名資訊進行區分。

在插件的使用過程中, 責任鍊設計模式展現在動态代理的層層嵌套的代理增強之中。 展現在

interceptorChain#pluginAll

方法中。 調用時會層層的進行代理。 mybatis 插件的原理-責任鍊和動态代理的展現

作者:阿進的寫字台

出處:https://www.cnblogs.com/homejim/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。