1. 程式人生 > >mybatis原始碼學習:外掛定義+執行流程責任鏈

mybatis原始碼學習:外掛定義+執行流程責任鏈

[toc] 前文傳送門: [mybatis原始碼學習:從SqlSessionFactory到代理物件的生成](https://www.cnblogs.com/summerday152/p/12773121.html) [mybatis原始碼學習:一級快取和二級快取分析](https://www.cnblogs.com/summerday152/p/12773135.html) [mybatis原始碼學習:基於動態代理實現查詢全過程](https://www.cnblogs.com/summerday152/p/12773141.html) # 一、自定義外掛流程 - 自定義外掛,實現Interceptor介面。 - 實現intercept、plugin和setProperties方法。 - 使用@Intercepts註解完成外掛簽名。 - 在主配置檔案註冊外掛。 ```java /** * 自定義外掛 * Intercepts:完成外掛簽名,告訴mybatis當前外掛攔截哪個物件的哪個方法 * * @author Summerday */ @Intercepts({ @Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class) }) public class MyPlugin implements Interceptor { /** * 攔截目標方法執行 * * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("MyPlugin.intercept getMethod: "+invocation.getMethod()); System.out.println("MyPlugin.intercept getTarget:"+invocation.getTarget()); System.out.println("MyPlugin.intercept getArgs:"+ Arrays.toString(invocation.getArgs())); System.out.println("MyPlugin.intercept getClass:"+invocation.getClass()); //執行目標方法 Object proceed = invocation.proceed(); //返回執行後的返回值 return proceed; } /** * 包裝目標物件,為目標物件建立一個代理物件 * * @param target * @return */ @Override public Object plugin(Object target) { System.out.println("MyPlugin.plugin :mybatis將要包裝的物件:"+target); //藉助Plugin類的wrap方法使用當前攔截器包裝目標物件 Object wrap = Plugin.wrap(target, this); //返回為當前target建立的動態代理 return wrap; } /** * 將外掛註冊時的properties屬性設定進來 * * @param properties */ @Override public void setProperties(Properties properties) { System.out.println("外掛配置的資訊:" + properties); } } ``` xml配置註冊外掛 ```xml ``` # 二、測試外掛 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200425194123283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) # 三、原始碼分析 ## 1、inteceptor在Configuration中的註冊 關於xml檔案的解析,當然還是需要從XMLConfigBuilder中查詢,我們很容易就可以發現關於外掛的解析: ```java private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //獲取到全類名 String interceptor = child.getStringAttribute("interceptor"); //獲取properties屬性 Properties properties = child.getChildrenAsProperties(); //通過反射建立例項 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); //設定屬性 interceptorInstance.setProperties(properties); //在Configuration中新增外掛 configuration.addInterceptor(interceptorInstance); } } } public void addInterceptor(Interceptor interceptor) { //interceptorChain是一個儲存interceptor的Arraylist interceptorChain.addInterceptor(interceptor); } ``` 此時初始化成功,我們在配置檔案中定義的外掛,已經成功加入interceptorChain。 ## 2、基於責任鏈的設計模式 我們看到chain這個詞應該並不會陌生,我們之前學習過的過濾器也存在類似的玩意,什麼意思呢?我們以Executor為例,當建立Executor物件的時候,並不是直接new Executor然後返回: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200425194348153.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) 在返回之前,他進行了下面的操作: ```java executor = (Executor) interceptorChain.pluginAll(executor); ``` 我們來看看這個方法具體幹了什麼: ```java public Object pluginAll(Object target) { //遍歷所有的攔截器 for (Interceptor interceptor : interceptors) { //呼叫plugin,返回target包裝後的物件 target = interceptor.plugin(target); } return target; } ``` 很明顯,現在它要從chain中一一取出interceptor,並依次呼叫各自的plugin方法,暫且不談plugin的方法,我們就能感受到責任鏈的功能:讓一個物件能夠被鏈上的任何一個角色寵幸,真好。
## 3、基於動態代理的plugin 那接下來,我們就成功進入我們自定義plugin的plugin方法: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200425194239427.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) ```java //看看wrap方法幹了點啥 public static Object wrap(Object target, Interceptor interceptor) { //獲取獲取註解的資訊,攔截的物件,攔截的方法,攔截方法的引數。 Map, Set> signatureMap = getSignatureMap(interceptor); //獲取當前物件的Class 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; } ``` getSignatureMap(interceptor)方法:其實就是獲取註解的資訊,攔截的物件,攔截的方法,攔截方法的引數。 ```java private static Map, Set> getSignatureMap(Interceptor interceptor) { //定位到interceptor上的@Intercepts註解 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); //如果註解不存在,則報錯 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } //獲取@Signature組成的陣列 Signature[] sigs = interceptsAnnotation.value(); Map, Set> signatureMap = new HashMap, Set>(); for (Signature sig : sigs) { //先看map裡有沒有methods set Set methods = signatureMap.get(sig.type()); if (methods == null) { //沒有再建立一個 methods = new HashSet(); //class:methods設定進去 signatureMap.put(sig.type(), methods); } try { //獲取攔截的方法 Method method = sig.type().getMethod(sig.method(), sig.args()); //加入到set中 methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } ``` getAllInterfaces(type, signatureMap)方法:確定是否為攔截物件 ```java private static Class[] getAllInterfaces(Class type, Map, Set> signatureMap) { Set> interfaces = new HashSet>(); while (type != null) { //介面型別 for (Class c : type.getInterfaces()) { //如果確實是攔截的物件,則加入interfaces set if (signatureMap.containsKey(c)) { interfaces.add(c); } } //從父介面中檢視 type = type.getSuperclass(); } //最後set裡面存在的元素就是要攔截的物件 return interfaces.toArray(new Class[interfaces.size()]); } ``` 我們就可以猜測,外掛只會對我們要求的物件和方法進行攔截。 ## 4、攔截方法的intercept(invocation) 確實,我們一路debug,遇到了Executor、ParameterHandler、ResultHandler都沒有進行攔截,然而,當StatementHandler物件出現的時候,就出現了微妙的變化,當我們呼叫代理的方法必然會執行其invoke方法,不妨來看看: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200425194152135.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) ok,此時進入了我們定義的intercept方法,感覺無比親切。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200425194158783.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) ```java //排程被代理物件的真實方法 public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } ``` 如果有多個外掛,每經過一次wrap都會產生上衣個物件的代理物件,此處反射呼叫的方法也是上衣個代理物件的方法。接著,就還是執行目標的parameterize方法,但是當我們明白這些執行流程的時候,我們就可以知道如何進行一些小操作,來自定義方法的實現了。 # 四、外掛開發外掛pagehelper 外掛文件地址:[https://github.com/pagehelper/Mybatis-PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) 這款外掛使分頁操作變得更加簡便,來一個簡單的測試如下: ## 1、引入相關依賴 ```xml com.github.pagehelper
pagehelper 5.1.2
``` ## 2、全域性配置 ```xml ``` ## 3、測試分頁 ```java @Test public void testPlugin(){ //查詢第一頁,每頁3條記錄 PageHelper.startPage(1,3); List all = userDao.findAll(); for (User user : all) { System.out.println(user); } } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200425194130480.png) # 五、外掛總結 參考:《深入淺出MyBatis技術原理與實戰》 - 外掛生成地是層層代理物件的責任鏈模式,其中設計反射技術實現動態代理,難免會對效能產生一些影響。 - 外掛的定義需要明確需要攔截的物件、攔截的方法、攔截的方法引數。 - 外掛將會改變MyBatis的底層設計,使用時務必謹慎。