Mybatis源碼 插件機制

簡介

插件是一種常見的擴展方式,大多數開源框架也都支持用戶通過添加自定義插件的方式來擴展或者改變原有的功能,MyBatis中也提供的有插件,雖然叫插件,但是實際上是通過攔截器(Interceptor)實現的,在MyBatis的插件模塊中涉及到責任鏈模式和JDK動態代理。

如何自定義實現插件?

step1:實現Interceptor接口;

step2:配置攔截器在全局配置文件

<plugins><plugin interceptor="com.dura.interceptor.FirstInterceptor"><-- 這里的這個屬性,我沒寫哈;不過,這里的用法倒是挺重要的,定義在攔截器中屬性  --><property name="testProp" value="1000"/></plugin></plugins>

step3:運行。

插件實現的原理?

初始化操作——全局配置文件解析

該方法用來解析全局配置文件中的plugins標簽,然后對應的創建Interceptor對象,并且封裝對應的屬性信息。最后調用了Configuration對象中的方法。 configuration.addInterceptor(interceptorInstance)

通過這個代碼我們發現我們自定義的攔截器最終是保存在了InterceptorChain這個對象中。而InterceptorChain的定義為

創建代理對象過程?

在解析的時候創建了對應的Interceptor對象,并保存在了InterceptorChain中,那么這個攔截器是如何和對應的目標對象進行關聯的呢

首先攔截器可以攔截的對象是Executor,ParameterHandler,ResultSetHandler,StatementHandler。

那么我們來看下這四個對象在創建的時候又什么要注意的

Executor

Executor在裝飾完二級緩存后會通過pluginAll來創建Executor的代理對象。

StatementHandler

  @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 注意,已經來到SQL處理的關鍵對象 StatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 獲取一個 Statement對象stmt = prepareStatement(handler, ms.getStatementLog());// 執行查詢return handler.query(stmt, resultHandler);} finally {// 用完就關閉closeStatement(stmt);}}// 進入創建的方法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;}//  可以看到statementHandler的代理對象

ParameterHandler

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// StatementType 是怎么來的? 增刪改查標簽中的 statementType="PREPARED",默認值 PREPAREDswitch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:// 創建 StatementHandler 的時候做了什么? >>delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}// 進入某個Handler看下  
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

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

執行過程

以Executor的query方法為例,當查詢請求到來的時候,Executor的代理對象是如何處理攔截請求的呢?我們來看下。當請求到了executor.query方法的時候


// 執行Plugin的invoke 方法
/*** 代理對象方法被調用時執行的代碼* @param proxy* @param method* @param args* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 獲取當前方法所在類或接口中,可被當前Interceptor攔截的方法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);}}//然后進入interceptor.intercept 會進入我們自定義的 FirstInterceptor對象中/*** 執行攔截邏輯的方法* @param invocation* @return* @throws Throwable*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("FirtInterceptor  攔截之前 ....");Object obj = invocation.proceed(); // 執行目標方法System.out.println("FirtInterceptor  攔截之后 ....");return obj;}

以上就是自定義的攔截器執行的完整流程。

如果我們有多個攔截器怎么辦

如果我們有多個自定義的攔截器,那么他的執行流程是怎么樣的呢?比如我們創建了兩個 Interceptor 都是用來攔截 Executor 的query方法,一個是用來執行邏輯A 一個是用來執行邏輯B的。

總結:Interceptor的相關對象作用

對象作用
Interceptor自定義插件需要實現接口,實現4個方法
InterceptChain配置的插件解析后會保存在Configuration的InterceptChain中
Plugin觸發管理類,還可以用來創建代理對象
Invocation對被代理類進行包裝,可以調用proceed()調用到被攔截的方法

應用場景分析——其實在說攔截器啦

作用描述實現方式
水平分表一張費用表按月度拆分為12張表。fee_202001-202012。當查詢條件出現月度(tran_month)時,把select語句中的邏輯表名修改為對應的月份表。對query update方法進行攔截在接口上添加注解,通過反射獲取接口注解,根據注解上配置的參數進行分表,修改原SQL,例如id取模,按月分表
數據脫敏手機號和身份證在數據庫完整存儲。但是返回給用戶,屏蔽手機號的中間四位。屏蔽身份證號中的出生日期。query——對結果集脫敏
菜單權限控制不同的用戶登錄,查詢菜單權限表時獲得不同的結果,在前端展示不同的菜單對query方法進行攔截在方法上添加注解,根據權限配置,以及用戶登錄信息,在SQL上加上權限過濾條件
黑白名單有些SQL語句在生產環境中是不允許執行的,比如like %%對Executor的update和query方法進行攔截,將攔截的SQL語句和黑白名單 進行比較,控制SQL語句的執行
全局唯一ID在高并發的環境下傳統的生成ID的方式不太適用,這時我們就需要考慮其他方式了創建插件攔截Executor的insert方法,通過UUID或者雪花算法來生成ID,并修改SQL中的插入信息

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899470.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899470.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899470.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android14 SystemUI中添加第三方AIDL

由于特殊需求&#xff0c;需要在SystemUI中添加第三方AIDL&#xff0c;去做一些客制化的修改。現在記錄一下AIDL添加的過程。 1.將AIDL文件拷貝到frameworks/base/packages/SystemUI/src/下&#xff0c;我要添加的AIDL文件是com/test/myctr/IDevicectr.aidl&#xff0c;添加后的…

Binlog、Redo log、Undo log的區別

一、binlog和redo log的區別 特性binlogredo log記錄對象記錄的是 MySQL 服務器的事務操作&#xff0c;針對的是整個數據庫實例。記錄的是 InnoDB 存儲引擎的數據頁變化&#xff0c;針對的是具體的存儲引擎層面。記錄內容記錄的是事務的邏輯操作&#xff0c;例如 SQL 語句&…

TDengine 中的異常恢復

簡介 本章主要介紹在 TDengine 執行命令過程中發生異常&#xff0c;如何手工終于執行的任務。可以終止連接&#xff0c;線上查詢及終止事務。 如果一個事務 在一個復雜的應用場景中&#xff0c;連接和查詢任務等有可能進入一種錯誤狀態或者耗時過長遲遲無法結束&#xff0c;…

全球化2.0 | ZStack舉辦香港Partner Day,推動AIOS智塔+DeepSeek海外實踐

2025年3月21日&#xff0c;云軸科技ZStack在香港成功舉辦了主題為“ZStack AIOS 智塔與 DeepSeek 私有化方案介紹及企業應用落地實踐”的 Partner Day 活動。此次活動吸引了眾多海外合作伙伴&#xff0c;共同探討 AI Infra 平臺在企業私有化 AI 中的應用與價值閉環。 ZStack CT…

ERP、MES和CRM三大企業系統的詳細介紹及對比分析

以下是關于ERP、MES和CRM三大企業系統的詳細介紹及對比分析&#xff1a; 1. ERP&#xff08;企業資源計劃&#xff0c;Enterprise Resource Planning&#xff09; 核心功能&#xff1a; 集成管理&#xff1a;財務、采購、庫存、生產、人力資源等核心業務流程資源優化&#xf…

(二十)Dart 中的多態

Dart 中的多態教程 一、多態的概念 多態是面向對象編程中的一個重要概念。它允許將子類類型的指針賦值給父類類型的指針&#xff0c;同一個函數調用會有不同的執行效果。換句話說&#xff0c;子類的實例可以賦值給父類的引用。多態的核心在于父類定義一個方法不去實現&#x…

【C++初階】第12課—list

文章目錄 1. list的構造2. list迭代器的常見接口2.1 list遍歷的迭代器接口2.2 list修改數據的迭代器接口2.3 list排序、逆序、合并相關操作的成員函數 3. 模擬實現list3.1 模擬實現list的構造3.2 模擬實現list的尾插3.3 模擬實現迭代器iterator3.4 模擬實現list的插入刪除3.5 模…

思維鏈技術(Chain-of-Thought, CoT)

思維鏈&#xff08;Chain-of-Thought, CoT&#xff09;是一種通過模擬人類逐步推理過程來提升大型語言模型&#xff08;LLM&#xff09;復雜任務表現的技術。其核心思想是讓模型在生成最終答案前&#xff0c;先輸出中間推理步驟&#xff0c;從而增強邏輯性和可解釋性。 1. 基礎…

谷粒微服務高級篇學習筆記整理---異步線程池

多線程回顧 多線程實現的4種方式 1. 繼承 Thread 類 通過繼承 Thread 類并重寫 run() 方法實現多線程。 public class MyThread extends Thread {Overridepublic void run() {System.out.println("線程運行: " Thread.currentThread().getName());} }// 使用 pub…

Windows學習筆記(4)關于MITRE

基本術語 APT&#xff08;威脅組&#xff0c;高級持續威脅&#xff09; TTP&#xff08;攻擊目的技術過程&#xff0c;戰術技術和程序&#xff09; ATT&CK框架 網站 https://attack.mitre.org/ CAR知識庫 MITRE Engage MITRE D3FEND 網址 https://d3fend.mitre.org/

Go 語言規范學習(2)

文章目錄 VariablesTypesBoolean typesNumeric typesString typesArray typesSlice typesStruct typesPointer typesFunction typesInterface typesBasic interfacesEmbedded interfacesGeneral interfaces【泛型接口】Implementing an interface【實現一個接口】 Map typesCha…

創意 Python 愛心代碼分享

創意 Python 愛心代碼分享 在編程中&#xff0c;用代碼表達創意和情感是一種非常有趣的方式。本文將分享幾段用 Python 編寫的愛心代碼&#xff0c;涵蓋簡單到復雜的實現方式&#xff0c;適合初學者和進階開發者。 1. 簡單愛心圖案 代碼實現 print("\n".join([&qu…

NLP高頻面試題(二十四)——RAG相關內容簡介

檢索增強生成&#xff08;Retrieval-Augmented Generation&#xff0c;簡稱 RAG&#xff09;是一種將信息檢索與生成模型相結合的技術&#xff0c;旨在提升大型語言模型的響應準確性、相關性和時效性。通過在生成過程中引入外部知識&#xff0c;RAG 能夠有效彌補 LLM 在知識局限…

Share01-WinCC文件越用越大?

為什么你們的經典WinCC項目在客戶電腦上運行的越來越慢&#xff1f;為什么查詢一個歷史曲線慢的要死&#xff1f;為什么重啟一下電腦畫面都要懷疑人生&#xff1f;具體原因可能多種多樣&#xff0c;但是極大可能是您的數據管理設置欠佳&#xff0c;那么閑話少敘&#xff0c;和小…

練習題:111

目錄 Python題目 題目 題目分析 需求理解 關鍵知識點 實現思路分析 代碼實現 代碼解釋 指定文件路徑和名稱&#xff1a; 定義要寫入的內容&#xff1a; 打開文件并寫入內容&#xff1a; 異常處理&#xff1a; 輸出提示信息&#xff1a; 運行思路 結束語 Python題…

2025_0327_生活記錄

昨晚正在玩手機&#xff0c;凌晨一點二十一分左右手機突然響起來&#xff0c;通知地震波將在5秒后到達海淀區。看著倒計時的數字不斷減小&#xff0c;橙色預警頁面不斷閃動&#xff0c;床猛地搖了幾下。那一刻&#xff0c;我的記憶被拉回了2008年。 上大學之前我在成都生活了1…

基于改進粒子群算法的多目標分布式電源選址定容規劃(附帶Matlab代碼)

通過分析分布式電源對配電網的影響&#xff0c;以有功功率損耗、電壓質量及分布式電源總容量為優化目標&#xff0c;基于模糊理論建立了分布式電源在配電網中選址定容的多目標優化模型&#xff0c;并提出了一種改進粒子群算法進行求解。在算例仿真中&#xff0c;基于IEEE-14標準…

雨云云應用測評!內測持續進行中!

大家好&#xff0c;時隔一個月&#xff0c;我們又見面了&#xff01; 最近&#xff0c;雨云推出了新型云應用&#xff08;RCA&#xff0c;Rainyun Cloud Application&#xff09;。 通過云應用&#xff0c;你可以快速創建可以外部訪問的應用&#xff0c;采用全新的面板和dock…

【研究方向】聯邦|自然語言

聯邦學習 Federated Learning,FL 分布式學習方案。 通過多個參與方&#xff08;client&#xff09; 聯邦計算 Federated Computing 聯邦計算(Federated Learning)是一種分布式 機器學習 方法,旨在解決數據隱私保護與數據孤島問題。 圖聯邦 Graph Neural Networks,GNNs 圖聯…

【算法day25】 最長有效括號——給你一個只包含 ‘(‘ 和 ‘)‘ 的字符串,找出最長有效(格式正確且連續)括號子串的長度。

32. 最長有效括號 給你一個只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最長有效&#xff08;格式正確且連續&#xff09;括號子串的長度。 https://leetcode.cn/problems/longest-valid-parentheses/ 2.方法二&#xff1a;棧 class Solution { public:int longestValid…