Mybatis 插件和動態代理

本次我們學習mybatis的插件,重點是理解他和動態代理的關係

動態代理簡單回顧

上節我們已經詳細的瞭解動態代理;主要是通過定義接口,通過

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
                                          InvocationHandler h)

方法,創建一個接口的代理。interfaces 表示代理也需要遵守的協議,還有h是實現
InvocationHandler協議的實例,其中有一個方法invoke,也是邏輯增強或者類功能增強的地方。具體請參考:使用JDK API實現動態代理和源碼分析
下面我們正式分析mybatis的插件

Mybatis 插件原理分析

@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class TenantInterceptor implements Interceptor {

我們最常見的插件應該就是這樣的,首先實現Interceptor方法接口;

 Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

我們關注上面上面這兩個方法。
首先我們給出答案,intercept方式是邏輯增強的方法,plugin是創建代理的方法。
但是直接看好像和我們動態代理沒有關係

生成代理

我們從 return Plugin.wrap(target, this);方法開始,也就是Interceptor 的第二個方法,在wrap方法中創建了代理

public static Object wrap(Object target, Interceptor interceptor) { // 1
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance( // 2
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  1. 方法的第一個參數object就是需要增強的對象,第二個就是Interceptor,因爲Interceptor的intercept有增強邏輯,例如修改SQL,分頁插件等等,是我們生成代理需要的。
  2. 正式的生成代理對象,其中用到了PluginPlugin類一定實現InvocationHandler接口。

邏輯增強 InvocationHandler

還是在Plugin中的invoke方法,也就是InvocationHandler接口方法。

  @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));//1
      }
      return method.invoke(target, args);//2
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  1. 我們實現的Interceptor實例的interceptor方法生效了,也就是邏輯增強
  2. 被代理類的方法執行

代理創建的觸發時機

在Mybatis中重要的接口ExecutorStatementHandler,其中的方法逐層調用,都是在爲執行SQL配置環境,其中StatementHandler就是。

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

上面是通過configuration配置類,創建StatementHandler,下面是具體的代碼。

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler c= new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 1
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler; // 2
  }
  1. 創建StatementHandler
  2. 使用interceptorChain增強statementHandler
    也就是每次我們通過SqlSession獲取Mapper,在Mapper上調用方法執行SQL時,都會觸發interceptorChain增強對象。
    interceptorChain是在configuration中存儲的,就是插件集合
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target); // 1
    }
    return target;
  }
  1. pluginAll方法,具體的增加就是創建調用我們編寫插件的plugin方法,創建代理對象,例如
public Object plugin(Object target) {
      return Plugin.wrap(target,this); // 創建實例
  }

總結

插件就是在調用Mapper方法時,通過動態代理對Mybatis的某些接口增強,通過Interceptor的 @Signature註解指定需要增強的接口方法.

還有很多細節,歡迎大家討論學習

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