MyBatis3教程 - MyBatis Interceptor源碼分析

本文分析使用的MyBatis 源代碼版本為3.4.1

在上一篇文章:MyBatis3教程 - MyBatis插件(Plugins)開發(fā) 中已經(jīng)介紹了如何去開發(fā)一個MyBatis 插件碗旅,本文將結(jié)合MyBatis 源碼來揭秘MyBatis Plugins內(nèi)部實(shí)現(xiàn)原理。

Mybatis3 插件采用責(zé)任鏈模式销部,通過動態(tài)代理組織多個攔截器(插件)眠饮,通過這些攔截器可以改變Mybatis的默認(rèn)行為(諸如SQL重寫之類的)奥帘。

先來看看上一篇文章中實(shí)現(xiàn)的插件,代碼如下:

package com.bytebeats.mybatis3.interceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Properties;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @date 2017-02-17 11:52
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
public class SQLStatsInterceptor implements Interceptor {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        logger.info("mybatis intercept sql:{}", sql);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        String dialect = properties.getProperty("dialect");
        logger.info("mybatis intercept dialect:{}", dialect);
    }
}

實(shí)現(xiàn)原理分析

Mybatis支持對Executor仪召、StatementHandler寨蹋、PameterHandler和ResultSetHandler 接口進(jìn)行攔截松蒜,也就是說會對這4種對象進(jìn)行代理。

下面以Executor接口為例已旧,org.apache.ibatis.executor.SimpleExecutor 在執(zhí)行doUpdate秸苗、doQuery、doQueryCursor方法時會執(zhí)行如下代碼:

Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);

順藤摸瓜运褪,我們來看看org.apache.ibatis.session.Configuration 類惊楼,其代碼如下:

public class Configuration {
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  
  /**對ParameterHandler 進(jìn)行攔截**/
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  /**對ResultSetHandler 進(jìn)行攔截**/
  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;
  }
  
  /**對StatementHandler 進(jìn)行攔截**/
  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 進(jìn)行攔截**/
  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  /**對Executor 進(jìn)行攔截**/
  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;
  }
  
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
}

我們重點(diǎn)關(guān)注這行代碼:

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

上面代碼功能是:對statementHandler 插入所有的Interceptor以便進(jìn)行攔截,InterceptorChain里保存了所有的攔截器秸讹,它在Configuration 對象被構(gòu)造出來的時候創(chuàng)建檀咙。

org.apache.ibatis.plugin.InterceptorChain 源代碼如下:

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

}

org.apache.ibatis.plugin.Interceptor 類的代碼如下:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

org.apache.ibatis.plugin.Invocation 代碼如下:

public class Invocation {

  private Object target;
  private Method method;
  private Object[] args;

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

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

org.apache.ibatis.plugin.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) {
    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);
    }
  }
}

責(zé)任鏈模式

責(zé)任鏈模式(Chain Of Responsibility Pattern )在 Wiki 上定義如下:

責(zé)任鏈模式在面向?qū)ο蟪淌皆O(shè)計里是一種軟件設(shè)計模式,它包含了一些命令對象和一系列的處理對象璃诀。每一個處理對象決定它能處理哪些命令對象弧可,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法劣欢。

23種設(shè)計模式中非常經(jīng)典的一個設(shè)計模式侣诺。

責(zé)任鏈模式應(yīng)用

1、Servlet FilterChain

package com.bytebeats.mario.web;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
public class CharacterEncodingFilter implements Filter {
    
    public void init(FilterConfig config) throws ServletException {
    }
 
    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) {
       chain.doFilter(request, response); 
    }
 
    public void destroy() {
    }
}

2氧秘、OkHttp Interceptors**

OkHttp Interceptorshttps://github.com/square/okhttp/wiki/Interceptors

這里寫圖片描述
這里寫圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末年鸳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丸相,更是在濱河造成了極大的恐慌搔确,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灭忠,死亡現(xiàn)場離奇詭異膳算,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弛作,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門涕蜂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人映琳,你說我怎么就攤上這事机隙。” “怎么了萨西?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵有鹿,是天一觀的道長。 經(jīng)常有香客問我谎脯,道長葱跋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮娱俺,結(jié)果婚禮上稍味,老公的妹妹穿的比我還像新娘。我一直安慰自己荠卷,他們只是感情好模庐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著僵朗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屑彻。 梳的紋絲不亂的頭發(fā)上验庙,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音粪薛,去河邊找鬼搏恤。 笑死,一個胖子當(dāng)著我的面吹牛熟空,可吹牛的內(nèi)容都是我干的藤巢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼息罗,長吁一口氣:“原來是場噩夢啊……” “哼掂咒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迈喉,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绍刮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挨摸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孩革,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡得运,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了熔掺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞬女,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诽偷,到底是詐尸還是另有隱情坤学,我是刑警寧澤疯坤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站深浮,受9級特大地震影響压怠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜飞苇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一布卡、第九天 我趴在偏房一處隱蔽的房頂上張望忿等。 院中可真熱鬧庵寞,春花似錦捐川、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至歌亲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悍缠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工飞蚓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滤港,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓趴拧,卻偏偏與公主長得像溅漾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子著榴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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

  • 1. 簡介 1.1 什么是 MyBatis 添履? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,520評論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理兄渺,服務(wù)發(fā)現(xiàn)缝龄,斷路器汰现,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • 昨天挂谍,突然,拿起勾線筆瞎饲,畫起了畫口叙。 “蝴蝶”——聽著就美的名字。 過去因?yàn)橐桓彼某叩墓すP畫嗅战,我焦頭爛額妄田。最后竟毫無...
    Nancymo觀自在閱讀 335評論 3 2
  • 我的60秒總結(jié): 今天我給大家分享的主題是:玩練是一種習(xí)慣 昨天公司錄關(guān)于一個操作說明的音頻講解,我處于好奇過去隨...
    大川手記閱讀 281評論 1 1
  • 讀的什么書:《結(jié)構(gòu)思考力》 閱讀有效時間:一個番茄鐘 閱讀中遇到了什么困難:同時在學(xué)慕課上關(guān)于教學(xué)的課程驮捍,時間...
    白告_why閱讀 118評論 0 0