Mybatis源碼解讀-插件

来源:https://www.cnblogs.com/konghuanxi/archive/2022/05/15/16273614.html
-Advertisement-
Play Games

插件允許對Mybatis的四大對象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)進行攔截 問題 Mybatis插件的註冊順序與調用順序的關係? 使用 在講源碼之前,先看看如何自定義插件。 mybatis-demo,官方文檔 ...


插件允許對Mybatis的四大對象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)進行攔截

問題

Mybatis插件的註冊順序與調用順序的關係?

使用

在講源碼之前,先看看如何自定義插件。

mybatis-demo官方文檔

  1. 創建插件類

    自定義插件類需要實現Interceptor

    // 註解配置需要攔截的類以及方法
    @Intercepts({
            @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
    })
    // 實現Interceptor介面
    public class SqlLogPlugin implements Interceptor {
    
        /**
         * 具體的攔截邏輯
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            long begin = System.currentTimeMillis();
            try {
                return invocation.proceed();
            } finally {
                long time = System.currentTimeMillis() - begin;
                System.out.println("sql 運行了 :" + time + " ms");
            }
        }
    
        /**
         * 判斷是否需要進行代理
         * 此方法有預設實現,一般無需重寫
         */
        /*@Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }*/
    
        /**
         * 自定義參數
         */
        @Override
        public void setProperties(Properties properties) {
            // 這是xml中配置的參數
            properties.forEach((k, v) -> {
                System.out.printf("SqlLogPlugin---key:%s, value:%s%n", k, v);
            });
        }
    }
    
  2. 註冊

    在配置文件註冊插件

    <plugins>
        <plugin interceptor="com.wjw.project.intercaptor.SqlLogPlugin">
            <property name="key1" value="root"/>
            <property name="key2" value="123456"/>
        </plugin>
    </plugins>
    
  3. 效果

    控制輸出

    SqlLogPlugin---key:key1, value:root
    SqlLogPlugin---key:key2, value:123456
    sql 運行了 :17 ms
    

源碼

原理:Mybatis四大對象創建時,都回去判斷是否滿足插件的攔截條件,滿足,則四大對象就會被Plugin類代理

源碼分3部分講。註冊、包裝、調用

  1. 註冊

    xml方式的註冊,是在XMLConfigBuilder#pluginElement完成的。

    不明覺厲的同學,請參考上一篇文章:Mybatis源碼解讀-配置載入和Mapper的生成

    // XMLConfigBuilder#pluginElement(XNode parent)
    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).getDeclaredConstructor().newInstance();
          interceptorInstance.setProperties(properties);
          // 將插件添加到配置的插件鏈中,等待後續使用
          configuration.addInterceptor(interceptorInstance);
        }
      }
    }
    

    configuration.addInterceptor做得操作很簡單

  2. 包裝

    上面講了插件的註冊,最後調用的是configuration.addInterceptor,最終調用的是InterceptorChain#addInterceptor

    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<>();
      /*
       * 每當四大對象創建時,都會執行此方法
       * 滿足攔截條件,則返回Plugin代理,否則返回原對象
       * @param target Mybatis四大對象之一
       */
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          // 調用每個插件的plugin方法,判斷是否需要代理
          target = interceptor.plugin(target);
        }
        return target;
      }
      // 將攔截器添加interceptors集合中存起來
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
    
      public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }
    

    我們案例是攔截StatementHandler,所以也以此為例

    /*
     * 這是創建StatementHandler的方法
     * Configuration#newStatementHandler
     */
    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之後,會調用InterceptorChain的pluginAll方法
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
    }
    

    那麼我們再仔細分析下pluginAll方法,pluginAll調用的是每個插件的plugin方法

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

    可以看到,最終調用的是Plugin.*wrap*

    /*
     * Plugin#wrap
     * 判斷是否滿足插件的攔截條件,是則返回代理類,否則返回原對象
     */
    public static Object wrap(Object target, Interceptor interceptor) {
      // 獲取插件的攔截信息(就是獲取@Intercepts註解的內容)
      Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
      Class<?> type = target.getClass();
      // 判斷是否滿足攔截條件
      Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
      if (interfaces.length > 0) {
        // 滿足攔截條件則返回Plugin代理對象
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
      }
      // 不滿足則返回原對象
      return target;
    }
    
  3. 調用

    在上一個包裝步驟提到,滿足條件會返回代理對象,即調用StatementHandler的所有方法,都會經過Plugininvoke方法,去看看

    // Plugin#invoke
    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)) {
          // 滿足攔截條件,則調用插件的intercept方法
          return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
      } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
      }
    }
    

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 忙裡偷閑,還在學校,趁機把後面的路由多出來的知識點學完 十.緩存路由組件 讓不展示的路由組件保持掛載,不被銷毀 在我們的前面案例有一個問題,都知道vue的路由當我們切換一個路由後,另一個路由就會被銷毀,如果我在一個路由寫了有一些input框 當我切換到另一個組件message很明顯這個時候news里 ...
  • Vue-mini 完整的Demo示例:[email protected]:xsk-walter/Vue-mini.git 一、Vue實例 構造函數: $option\ $el\ $data 判斷是否存在 通過 || 邏輯運算符; _ProxyData 遍歷所有data屬性,並註入到vue實例中; 判斷是否 ...
  • 數組對象 創建數組 創建方式1: var arrname = [元素0,元素1,….]; // var arr=[1,2,3]; 創建方式2: var arrname = new Array(元素0,元素1,….); // var test=new Array(100,"a",true); 數組方法 ...
  • Eclipse WindowBuilder(SWT)插件安裝及初次使用(萌新) 一、插件安裝 (有VPN的掛VPN,伺服器在外網更新下載比較慢) 1.首先更新到最新版本 點擊Help,點擊check for update,右下角顯示查詢進度,查詢完畢會顯示最新版內容。全部更新 2.還是在help內, ...
  • 時間真的好快呀!一晃眼五一假期就結束了,假期大家都玩的開心不?今天我要給大家分享幾個練手 的基礎案例,保證大家都沒有玩過。話不多說,來來來… 一、超市購買薯片 python學習交流Q群:906715085##### # 用戶輸入薯片的單價 danjia = float(input("薯片的單價")) ...
  • 這節課是巡安似海PyHacker編寫指南的《Sql註入腳本編寫》有些註入點sqlmap跑不出,例如延時註入,實際延時與語句延時時間不符,sqlmap就跑不出,這就需要我們自己根據實際情況編寫腳本來註入了。文末,涉及了sqlmap tamper編寫,所以需要一定的python基礎才能看懂。喜歡用Pyt... ...
  • 通過URL的尾碼發現了Spring中的useSuffixPatternMatch這個參數 ...
  • 本文大部分來自官方教程的Google翻譯 但是加了一點點個人的理解和其他相關知識 轉載請註明 原文鏈接 :https://www.cnblogs.com/Multya/p/16273753.html 官方教程: https://www.sfml-dev.org/tutorials/2.5/ 持續更新 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...