Mybatis之插件實現(xiàn)原理

為什么要有插件

可以在映射語句執(zhí)行前后加一些自定義的操作,比如緩存失尖、分頁等 <br />

可以攔截哪些方法

默認情況下善榛,Mybatis允許使用插件來攔截的方法有:

  • Executor:update戴而、query砾肺、flushStatements挽霉、commit、rollback变汪、getTransaction侠坎、close、isClosed疫衩。
    實現(xiàn)類:SimpleExecutor/BatchExecutor/ReuseExecutor/CachingExecutor
  • ParameterHandler:getParameterObject硅蹦、setParameters。
    實現(xiàn)類:DefaultParameterHandler
  • ResultSetHandler:handleResultSets闷煤、handleOutputParameters童芹。
    實現(xiàn)類:DefaultResultSetHandler
  • StatementHandler:prepare、parameterize鲤拿、batch假褪、update、query近顷。
    實現(xiàn)類:CallableStatementHandler/PreparedStatementHandler/SimpleStatementHandler/RoutingStatementHandler

如何自定義插件

只需實現(xiàn)Interceptor接口生音,并指定要攔截的方法簽名

@Intercepts({
    @Signature(
        type=Executor.class,method="update",args={ MappedStatement.class,Object.class })
})
public class ExamplePlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
       //自定義實現(xiàn)
       return invocation.proceed();
    }
    public Object plugin(Object target){
        return Plugin.wrap(target,this)
    }
    public void setProperties(Properties properties){
      //傳入配置項
      String size = properties.getProperty("size");
    }
}
<!-- mybatis-config.xml -->
<plugins>
    <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <!-- 這里的配置項就傳入setProperties方法中 -->
        <property name="size" value="100">
    </plugin>
</plugins>

攔截器實現(xiàn)原理

如果了解Mybatis的攔截器實現(xiàn)原理,可以在以后的工作中也可使用該方法實現(xiàn)自己的攔截器


插件包
//攔截器接口窒升,供外部實現(xiàn)缀遍,實現(xiàn)該接口就定義了一個插件
public interface Interceptor {
  //攔截方法,可以將自定義邏輯寫在該方法中
  Object intercept(Invocation invocation) throws Throwable;
  //包裝成插件饱须,一般Plugin.wrap(target,this)就行了
  Object plugin(Object target);
  //傳入自定義配置參數(shù)
  void setProperties(Properties properties);
}
攔截器上定義的注解
@Intercepts:攔截器注解域醇,包括一個或多個@Signature,攔截的目標類信息
@Signature:攔截的目標類信息蓉媳,包括type譬挚、method、args酪呻,一個@Intercepts中可包含多個@Signature

public class Invocation {
  private Object target;//目標對象
  private Method method;//調(diào)用方法
  private Object[] args;//方法形參列表
  //省略get和set方法
  //執(zhí)行調(diào)用减宣,基于動態(tài)代理,在Interceptor的intercept方法中一定要調(diào)用該方法
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}
//動態(tài)代理實現(xiàn)
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();
    //目標類所有接口是否有signatureMap中定義的Class
    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);
    }
  }
  //獲取攔截器上的SignatureMap
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    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());//重復(fù)定義的只生效一個
      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) {//獲取type上的所有接口
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {//這里不判斷Method,只判斷Class<?>
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

在配置文件中定義的過濾器漆腌,都保存在Configuration類的interceptorChain中,這個類保存了mybatis的所有配置阶冈,interceptorChain類中保存中所有Interceptor集合組成的攔截器鏈屉凯,這個鏈是如何添加進去的呢?請看源碼眼溶。

  //XMLConfigBuilder類中解析mybatis-config.xml 核心方法parseConfiguration(XNode root)
  pluginElement(root.evalNode("plugins"));//插件配置項

 private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
    //遍歷 plugins的子節(jié)點plugin
    for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");//獲取interceptor屬性值
        Properties properties = child.getChildrenAsProperties();//獲取plugin屬性值
        //創(chuàng)建攔截器實例悠砚,這里interceptor值也可以是typeAlias注冊的簡名
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //設(shè)置屬性項
        interceptorInstance.setProperties(properties);
        //添加到interceptorChain中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  //Configuration類,添加攔截器
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

攔截的哪些接口

  //SQL語句處理器
  public interface StatementHandler {
    //預(yù)備工作
    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
    //參數(shù)處理
    void parameterize(Statement statement) throws SQLException;
    //批量處理
    void batch(Statement statement)  throws SQLException;
    //更新處理
    int update(Statement statement) throws SQLException;
    //查詢處理
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
  }
  //返回集處理器
  public interface ResultSetHandler {
    //處理返回結(jié)果
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    //處理輸出參數(shù)
    void handleOutputParameters(CallableStatement cs) throws SQLException;
  }
  //參數(shù)處理器
  public interface ParameterHandler {
     
    Object getParameterObject();

    void setParameters(PreparedStatement ps) throws SQLException;
  }

如何攔截這些接口

//創(chuàng)建相應(yīng)Handler時會將所有攔截器通過動態(tài)代理方式返回代理Handler
public class Configuration {

  //創(chuàng)建ParameterHandler(參數(shù)處理器)
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
        Object parameterObject, BoundSql boundSql) {
  // 根據(jù)指定Lang(默認RawLanguageDriver),創(chuàng)建ParameterHandler堂飞,將實際參數(shù)傳遞給JDBC語句
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(
        mappedStatement, parameterObject, boundSql);
    //返回代理實例
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  //創(chuàng)建ResultSetHandler(結(jié)果處理器)
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, 
     RowBounds rowBounds,ParameterHandler parameterHandler,
     ResultHandler resultHandler,BoundSql boundSql) {
    //默認使用DefaultResultSetHandler創(chuàng)建ResultSetHandler實例
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement,
       parameterHandler, resultHandler, boundSql, rowBounds);
    //返回代理實例
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  //創(chuàng)建StatementHandler(SQL語句處理器)
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, 
    Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //默認使用RoutingStatementHandler(路由作用)
    //創(chuàng)建指定StatementHandler實例(默認SimpleStatementHandler)
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, 
          parameterObject, rowBounds, resultHandler, boundSql);
    //返回代理實例
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  //創(chuàng)建Executor(執(zhí)行器)
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //獲取executorType灌旧,默認是SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    //這一行感覺有點多余啊
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {  //批量執(zhí)行
      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;
  }
}
//執(zhí)行器
public interface Executor {
    //更新
    int update(MappedStatement ms, Object parameter) throws SQLException;
    //查詢(先查緩存)
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
          ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    //查詢
    <E> List<E> query(MappedStatement ms, Object parameter, 
          RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    //查詢游標
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) 
          throws SQLException;
    //刷新Statement
    List<BatchResult> flushStatements() throws SQLException;
    //提交事務(wù)
    void commit(boolean required) throws SQLException;
    //回滾事務(wù)
    void rollback(boolean required) throws SQLException;
    //創(chuàng)建緩存key
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject,RowBounds rowBounds, 
          BoundSql boundSql);
    //是否存在key
    boolean isCached(MappedStatement ms, CacheKey key);
    //清除本地緩存
    void clearLocalCache();
    //延遲加載
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, 
          Class<?> targetType);
    //獲取事務(wù)
    Transaction getTransaction();
    //關(guān)閉連接
    void close(boolean forceRollback);
    //是否關(guān)閉
    boolean isClosed();
    //設(shè)置Executor
    void setExecutorWrapper(Executor executor);
}

總結(jié)

當然具體實現(xiàn)肯定不止這么多代碼,如果需要了解绰筛,需要自行看源碼枢泰,下面坐下總結(jié)。
1.攔截器實現(xiàn)
Interceptor接口供插件實現(xiàn)铝噩,@Intercepts注解在插件實現(xiàn)上衡蚂,表示這是一個插件類并配置將要攔截哪些方法,@Signature定義將要攔截的方法信息,如名稱/類型/形參列表,Plugin類實現(xiàn)了InvocationHandler接口毛甲,是動態(tài)代理的具體實現(xiàn)年叮,Invocation類包裝了攔截的目標實例,InterceptorChain保存所有攔截器玻募。
2.如何實現(xiàn)攔截
創(chuàng)建目標實例只损,比如A a = new A();
Interceptor interceptor = new LogInterceptor();//如果攔截a中的save方法
將A b = (A)interceptor.plugin(a);這里b就是a的代理實例,在調(diào)用a中的save方法時七咧,實際將調(diào)用interceptor的intercept方法跃惫,在該方法中一定要調(diào)用Invocation的proceed方法并將返回值返回。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末艾栋,一起剝皮案震驚了整個濱河市爆存,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蝗砾,老刑警劉巖终蒂,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遥诉,居然都是意外死亡拇泣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門矮锈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霉翔,“玉大人,你說我怎么就攤上這事苞笨≌洌” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵瀑凝,是天一觀的道長序芦。 經(jīng)常有香客問我,道長粤咪,這世上最難降的妖魔是什么谚中? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮寥枝,結(jié)果婚禮上宪塔,老公的妹妹穿的比我還像新娘。我一直安慰自己囊拜,他們只是感情好某筐,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冠跷,像睡著了一般南誊。 火紅的嫁衣襯著肌膚如雪身诺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天抄囚,我揣著相機與錄音霉赡,去河邊找鬼。 笑死怠苔,一個胖子當著我的面吹牛同廉,可吹牛的內(nèi)容都是我干的仪糖。 我是一名探鬼主播柑司,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锅劝!你這毒婦竟也來了攒驰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤故爵,失蹤者是張志新(化名)和其女友劉穎玻粪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诬垂,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡劲室,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了结窘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片很洋。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖隧枫,靈堂內(nèi)的尸體忽然破棺而出喉磁,到底是詐尸還是另有隱情,我是刑警寧澤官脓,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布协怒,位于F島的核電站,受9級特大地震影響卑笨,放射性物質(zhì)發(fā)生泄漏孕暇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一赤兴、第九天 我趴在偏房一處隱蔽的房頂上張望芭商。 院中可真熱鬧,春花似錦搀缠、人聲如沸铛楣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽簸州。三九已至鉴竭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岸浑,已是汗流浹背搏存。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矢洲,地道東北人璧眠。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像读虏,于是被迫代替她去往敵國和親责静。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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

  • MyBatis提供了一種插件(plugin)的功能盖桥,雖然叫做插件灾螃,但其實這是攔截器功能。那么攔截器攔截MyBati...
    七寸知架構(gòu)閱讀 3,254評論 3 54
  • 1. 簡介 1.1 什么是 MyBatis 揩徊? MyBatis 是支持定制化 SQL腰鬼、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,523評論 0 4
  • 記錄是一種精神,是加深理解最好的方式之一塑荒。 最近看了下Mybatis的源碼熄赡,分析了Mybatis插件的實現(xiàn)方式,在...
    曹金桂閱讀 17,974評論 15 52
  • 我曾經(jīng)所天真的那些 都被時間驗證了遍 虛假的不切實際的 最終淪為齒邊的黑點 不能再在大雨中肆無忌憚的奔跑 不能再在...
    黃小骨閱讀 102評論 0 1
  • 三天一小吵齿税,五天一大吵彼硫,有時候感覺吵架已經(jīng)是家常便飯,有時候挺不能理解女生所謂的安全感到底是什么偎窘。 沒有秒回沒有買...
    wensmily閱讀 207評論 0 0