mybatis 插件的原理-責任鏈和動態代理的體現 1 攔截哪些方法 2 如何代理 3 代理對象 4 責任鏈設計模式

責任鏈設計模式理解起來很簡單, 網上找個例子看看即可。

mybatis 插件的原理使用的是動態代理和責任鏈來實現的。

1 攔截哪些方法

可以通過註解 InteceptsSignature 來進行指定攔截哪些方法。 然而, 並不是說所有的方法都可以攔截的。

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 通過這些對象實現對數據庫的操作, 結果的處理。 因此, 從流程上來說, 是攔截這幾個對象中的方法是有非常重要的作用的。

而在源碼上的體現呢, 就在 Configuration 類中, 這個類的重要性不做過多的闡述, 可以看看前面的文章。

在總 xml 解析成 Configuration 過程中, 需要 new 出以上的幾個類。而以上的幾個類在後面都會調用 interceptorChain#pluginAll 方法。

2 如何代理

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.

3 代理對象

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 的動態代理是接口級別的。 因此, 其代理了類的所有接口的方法。 然而並不是所有的方法都是需要被代理的, 因此, 在方法中通過註解中的簽名信息進行區分。

4 責任鏈設計模式

在插件的使用過程中, 責任鏈設計模式體現在動態代理的層層嵌套的代理增強之中。 體現在interceptorChain#pluginAll 方法中。 調用時會層層的進行代理。 mybatis 插件的原理-責任鏈和動態代理的體現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章