《深入淺出MyBatis技術原理與實戰》——7. 插件
在第6章討論了四大運行對象的運行過程,在Configuration對象的創建方法裏我們看到了MyBatis用責任鏈去封裝它們。
7.1 插件接口
在MyBatis中使用插件,我們必須使用接口Interceptor,先來看看它的定義和各個方法的含義:
在接口各中,運用了3個方法,這3個方法的含義是:
這裏看到了插件的骨架,這樣的模式我們稱為模版模式,就是提供一個骨架,並且告知骨架中的方法是幹什麽用的,由開發者來完成它。
7.2 插件的初始化
插件的初始化實在MyBatis初始化的時候完成的,這點可以通過XMLConfigBuilder中的代碼知道:
在解析配置文件的時候,在MyBatis的上下文初始化過程中,就開始讀入插件節點和我們配置的參數,同時使用反射技術生成對應的插件實例,然後調用插件方法中的setProperties方法,設置我們配置的參數,然後將插件實例保存到配置對象中,以便讀取和使用它。所以插件的實例對象是一開始就被初始化的,而不是用到的時候才初始化的,我們使用它的時候,直接拿出來就可以了,這樣有助於性能的提高。
再來看看插件在Configuration對象裏是怎樣保存的:
InterceptorChain在Configuration裏面是一個屬性,它裏面有個addInterceptor方法:
顯然,完成初始化的插件就保存在這個List對象裏面等待將其取出使用。
7.3 插件的代理和反射設計
插件用的是責任鏈模式。就是一個對象,在MyBatis中可能是四大對象中的一個,在多個角色中傳遞,處在傳遞鏈上的任何角色都有處理它的機會。
MyBatis的責任鏈是由InterceptorChain去定義的,MyBatis在創建執行器時用到這樣的代碼:
來看看pluginAll()方法是如何實現的:
plugin方法是生成代理對象的方法,當它取出插件的時候是從Configuration對象中去取的。從第一個對象(四大對象中的一個)開始,將對象傳遞給了plugin方法,然後返回一個代理,如果存在第二個插件,那麽就拿到第一個代理對象的代理,傳遞給plugin方法再返回第一個代理對象的代理......以此類推,有多少個攔截器就生成多少個代理對象。這樣有多少個攔截器就生成多少個代理對象。
MyBatis中提供了一個常用的工具類來生成代理對象,它便是Plugin類。Plugin類實現了InvocationHandler接口,采用的是JDK動態代理,我們首先看看這個類的兩個十分重要的方法:
看以看到它是一個動態代理對象,其中wrap方法為我們生成這個對象的動態代理對象。至於invoke方法,如果使用這個類為插件生成代理對象,那麽代理對象在調用方法的時候就會進入到invoke方法中。在invoke方法中,如果存在簽名的攔截方法,插件的intercept方法就會被我們在這裏調用,然後就返回結果。如果不存在簽名的方法,那麽將直接調度我們要執行的方法。
我們創建一個Invocation對象,其構造方法的參數包括被代理對象、方法及其參數。Invocation對象進行初始化,它有一個proceed()方法:
這個方法就是調度被代理對象的真實方法。現在假設有n個插件,我們知道第一個傳遞的參數是四大對象本身,然後調用一次wrap方法產生第一個代理對象,而這裏的反射就是反射四大對象本身的真實方法。如果有第二個插件,我們會將第一個代理對象傳遞給wrap方法,生成第二個代理對象,這裏的反射就是指第一個代理對象的invoke方法,依次類推直至最後一個代理對象。如果每一個代理對象都調用這個proceed方法,那麽最後四大對象本身的方法也會被調用,只是它會從最後一個代理對象的invoke方法運行到第一個代理對象的invoke方法,直至四大對象的真實方法。
7.4 常用的工具類——MetaObject
可以有效的讀取或者修改一些重要的對象屬性。它有3個方法常常用到:
我們可以通過使用它來給四大對象的某些屬性賦值從而滿足我們的需要。例如,攔截StatementHandler對象,我們需要先獲取她要執行的SQL修改它的一些值。這個時候我們可以使用MetaObject,如:
攔截的StatementHandler實際上是RoutingStatementHandler對象,它的delegate屬性才是真實服務的StatementHandler,真實的StatementHandler有一個屬性BoundSql,它下面又有一個屬性sql。所以才有了路徑delegate.boundSql.sql。我們就可以通過這個路徑獲取或者修改對應運行時的SQL。
7.5 插件開發過程實例
限制每一條SQL返回數據的行數。限制的行數是個可配置的參數,業務可以根據自己的需要去配置。
7.5.1 確定需要攔截的簽名
從Plugin源碼中我們可以看到它需要註冊簽名才能夠運行插件。簽名需要確定一些要素。
(1) 確定需要攔截的簽名
那麽我們需要攔截的是StatementHandler對象,應該在預編譯SQL之前,修改SQL使得返回數量被限制。
(2) 攔截方法和參數
查詢是通過Executor調度StatementHandler來完成的。調度StatementHandler的prepare方法預編譯SQL,於是我們需要攔截的方法便是prepare方法,在此之前完成SQL的重新編譯。先來看看那StatementHandler接口的定義:
以上的任何方法都可以被攔截。從接口的定義而言,prepare方法有一個參數Connection對象,因此我們按如下代碼來設計攔截器:
[email protected]@Signature是註冊攔截器簽名的地方,只有簽名滿足條件才能被攔截,type可以使四大對象中的一個,這裏是StatementHandler。method代表要攔截四大對象的某一種接口方法,而args則表示該方法的參數。
7.5.4 插件實例
限制查詢返回的數據量。需要攔截的是StatementHandler對象
在setProperties方法中可以讀入配置給插件的參數,一個是數據庫的名稱,另外一個是限制的記錄數。在plugin方法裏,使用了MyBatis提供的類來生成代理對象。那麽插件就會進入plugin的invoke方法,它最後會使用到攔截器的intercept方法。現在我們需要在MyBatis配置文件裏面配置才能運行這個插件:
註意:
《深入淺出MyBatis技術原理與實戰》——7. 插件