【深刻淺出MyBatis筆記】插件

插件

一、插件接口

在MyBatis中使用插件,咱們必須實現接口Interceptor。java

public interface Interceptor {
  // 它將直接覆蓋你所攔截對象原有的方法,所以它是插件的核心方法。
  // Intercept裏面有個參數Invocation對象,經過它能夠反射調度原來對象的方法
  Object intercept(Invocation invocation) throws Throwable;

  // 做用是給被攔截對象生成一個代理對象,並返回它。target是被攔截對象
  Object plugin(Object target);

  // 容許在plugin元素中配置所需參數,方法在插件初始化的時候就被調用了一次
  void setProperties(Properties properties);

}

二、插件初始化

插件的初始化是在MyBatis初始化的時候完成的。ide

public class XMLConfigBuilder extends BaseBuilder {
  ......
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
}

在解析配置文件的時候,在MyBatis的上下文初始化過程當中,就開始讀入插件節點和咱們配置的參數,同時使用反射技術生成對應的插件實例,而後調用插件方法中的setProperties方法,設置咱們配置的參數,而後將插件實例保存到配置對象中,以便讀取和使用它。ui

插件在Configuration對象中的保存:this

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

三、插件的代理和反射設計

插件用的是責任鏈模式,MyBatis的責任鏈是由interceptorChain去定義的。在MyBatis建立Executor執行器的時候,咱們能夠看到有以下的一段代碼:插件

executor = (Executor) interceptorChain.pluginAll(executor);

再看下Interceptor的pluginAll方法:設計

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // plugin方法是生成代理對象的方法
      // 能夠看出來,若是有多個插件的話,會生成多層代理的代理對象
      target = interceptor.plugin(target);
    }
    return target;
  }
  ......
}

MyBatis爲咱們提供了Plugin類用於生成代理對象。代理

public class Plugin implements InvocationHandler {
  ......

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

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 若是存在簽名的攔截方法,插件的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);
    }
  }
}

在調用插件的攔截方法時,能夠看到傳遞了一個新建立的Invocation對象。code

interceptor.intercept(new Invocation(target, method, args));

Invocation類封裝了被代理的對象、方法及其參數。對象

public class Invocation {
  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  // 這個方法會調度被代理對象的真實方法, 因此咱們經過這個方法直接調用被代理對象原來的方法
  // 若是多個插件的話,咱們知道會生成多層代理對象,那麼每層被代理均可以經過Invocation調用這個proceed方法,
  // 因此在多個插件的環境下,調度proceed()方法時,MyBatis老是從最後一個代理對象運行到第一個代理對象,
  // 最後是真實被攔截的對象方法被運行
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}
相關文章
相關標籤/搜索