Mybatis源碼解析之Interceptor

[上一篇]:Mybatis源碼解析之SqlSession來自何方

上一篇中我們知道了sqlSession是最后通調(diào)用sessionFactory.openSession(executorType)得到的帜讲,這里的sessionFactory為DefaultSqlSessionFactory该贾。

在openSession(executorType)的時候還進行了本文主要講的對攔截器處理的重要操作唁毒。

問題

Mybatis源碼解析之配置解析中看到了MyBatis將配置的plugins加入了Configuration里的interceptorChain里漾脂,那么這些plugins是如何執(zhí)行生效的呢赁咙?

揭秘源碼-答案

首先介紹對Executor進行攔截

@Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     //這里創(chuàng)建了一個executor
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

從上面代碼看到在獲取sqlSession時,創(chuàng)建了一個Executor并讓sqlSession持有票彪,因為我們關(guān)心如何MyBatis如何對Executor進行攔截的揭朝,所以先看Configuration中創(chuàng)建Executor的地方。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

首先欣除,根據(jù)ExecutorType創(chuàng)建了不同類型的Executor(默認的執(zhí)行器SIMPLE住拭、執(zhí)行器重用REUSE、執(zhí)行器重用語句批量更新BATCH)历帚。
在倒數(shù)第二行看到了調(diào)用了Interceptor鏈中的pluginAll方法滔岳,傳入的是executor

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

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

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

pluginAll方法依次調(diào)用了配置的interceptor的plugin方法,傳入的是executor挽牢,以MyBatis實現(xiàn)的PageInterceptor為例谱煤,看下plugin方法,發(fā)現(xiàn)關(guān)鍵的最后一行Plugin.wrap(o, this);注意傳入的是executor與Interceptor本身

PageInterceptor部分源碼

 public Object plugin(Object o) {
        try {
            this.additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
            this.additionalParametersField.setAccessible(true);
        } catch (NoSuchFieldException var3) {
            throw new RuntimeException("Mybatis BoundSql版本問題禽拔,不存在additionalParameters", var3);
        }

        return Plugin.wrap(o, this);
    }

再看下Plugin下的源碼

public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private 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;
  }

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

  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<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      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;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    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()]);
  }

}

wrap方法
1.首先會獲取interceptor上的注解
2.再獲取需要攔截的接口里的所有接口方法刘离,(如下:PageInterceptor上的注解,signatureMap里只包含一個key為Executor.class睹栖,值是set<Method>這個set只包含一個名為query參數(shù)為args里的參數(shù)的方法)

@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class }) })

3.最后使用JDK動態(tài)代理對Executor接口里的所有方法進行了代Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));)

這個動態(tài)代理的handler就是Plugin硫惕,所以當(dāng)對數(shù)據(jù)庫進行操作,調(diào)用Executor里的方法時野来,都會調(diào)用Plugin的invoke方法恼除。

invoke()
1.首先通過調(diào)用的方法所在類獲取了需要被interceptor攔截的方法有哪些
2.如果包含了該方法就調(diào)用攔截器的intercept進行處理
3.最后調(diào)用實際方法

就是這樣就讓一個對Executor的攔截器生效了。

如果有多個攔截器曼氛,由于在InterceptorChain 的源碼pluginAll(Object object)通過for循環(huán)調(diào)用豁辉,每次返回的是對傳入對象的jdk動態(tài)代理令野,然后又將代理對象傳入,依次下去秋忙,這樣就將Excutor對象包裝了一層一層的Interceptor彩掐,在調(diào)用的時候,也會一層一層調(diào)用invoke方法灰追,只有被攔截的方法才會進入interceptor的intercept()方法。

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

我們看到將對Executor的攔截器加入是在Configuration.newExecutor,對以下三種類型也是在Configuration中
對StatementHandler進行攔截
對ParameterHandler進行攔截
對ResultSetHandler進行攔截
貼出源碼

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

具體原理與Executor一致狗超,簡單說下是在什么時候調(diào)用這些方法弹澎。newStatementHandler的調(diào)用是在所有繼承了BaseExecutor的類里

當(dāng)Executor執(zhí)行相關(guān)操作時會調(diào)用具體Executor的相關(guān)方法,以SimpleExecutor為例

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    return Collections.emptyList();
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

}

每個進行相關(guān)數(shù)據(jù)庫操作的方法都有獲取StatementHandler的一步努咐。在這一步會進行攔截器的設(shè)置苦蒿。
而對ParameterHandler進行攔截與對ResultSetHandler進行攔截設(shè)置調(diào)用的Configuration相應(yīng)的new方法是在newStatementHandler里
new RoutingStatementHandler ->new PreparedStatementHandler->super(也就是BaseStatementHandler)

 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

總結(jié):

Mybatis中Interceptor的原理就是MyBatis通過jdk動態(tài)代理將需要的對象(一般使用默認的Plugin.wrap)進行代理,并記錄哪些方法需要進行攔截處理渗稍,最后在invoke的時候查找是否是需要攔截處理的方法佩迟,是就調(diào)用intercept方法

如有錯誤,歡迎各位大佬斧正竿屹!
[下一篇]:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末报强,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拱燃,更是在濱河造成了極大的恐慌秉溉,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,185評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碗誉,死亡現(xiàn)場離奇詭異召嘶,居然都是意外死亡,警方通過查閱死者的電腦和手機哮缺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評論 3 385
  • 文/潘曉璐 我一進店門弄跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尝苇,你說我怎么就攤上這事铛只。” “怎么了茎匠?”我有些...
    開封第一講書人閱讀 157,684評論 0 348
  • 文/不壞的土叔 我叫張陵格仲,是天一觀的道長。 經(jīng)常有香客問我诵冒,道長凯肋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,564評論 1 284
  • 正文 為了忘掉前任汽馋,我火速辦了婚禮侮东,結(jié)果婚禮上圈盔,老公的妹妹穿的比我還像新娘。我一直安慰自己悄雅,他們只是感情好驱敲,可當(dāng)我...
    茶點故事閱讀 65,681評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宽闲,像睡著了一般众眨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上容诬,一...
    開封第一講書人閱讀 49,874評論 1 290
  • 那天娩梨,我揣著相機與錄音,去河邊找鬼览徒。 笑死狈定,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的习蓬。 我是一名探鬼主播纽什,決...
    沈念sama閱讀 39,025評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躲叼!你這毒婦竟也來了芦缰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,761評論 0 268
  • 序言:老撾萬榮一對情侶失蹤押赊,失蹤者是張志新(化名)和其女友劉穎饺藤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體流礁,經(jīng)...
    沈念sama閱讀 44,217評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡涕俗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,545評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了神帅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片再姑。...
    茶點故事閱讀 38,694評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖找御,靈堂內(nèi)的尸體忽然破棺而出元镀,到底是詐尸還是另有隱情,我是刑警寧澤霎桅,帶...
    沈念sama閱讀 34,351評論 4 332
  • 正文 年R本政府宣布栖疑,位于F島的核電站,受9級特大地震影響滔驶,放射性物質(zhì)發(fā)生泄漏遇革。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,988評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萝快。 院中可真熱鬧锻霎,春花似錦、人聲如沸揪漩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奄容。三九已至冰更,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昂勒,已是汗流浹背冬殃。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叁怪,地道東北人。 一個月前我還...
    沈念sama閱讀 46,427評論 2 360
  • 正文 我出身青樓深滚,卻偏偏與公主長得像奕谭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痴荐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,580評論 2 349

推薦閱讀更多精彩內(nèi)容