源碼分析Mybatis系列目錄:spring
源碼分析Mybatis MapperProxy初始化【圖文並茂】sql
源碼分析Mybatis MappedStatement的建立流程apache
【圖文並茂】Mybatis執行SQL的4大基礎組件詳解數組
有了 Mybatis執行SQL的4大基礎組件詳解 與 源碼解析MyBatis Sharding-Jdbc SQL語句執行流程詳解兩篇文章的鋪墊,本文將直奔主題:Mybatis插件機制。微信
舒適提示:本文也是以提問式閱讀與探究源碼的技巧展現。mybatis
從前面的文章咱們已經知道,Mybatis在執行SQL語句的擴展點爲Executor、StatementHandler、ParameterHandler與ResultSetHandler,咱們本節將以Executor爲入口,向你們展現Mybatis插件的擴展機制。app
咱們先來看回顧一下Mybatis Executor的建立入口。運維
1public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 2 executorType = executorType == null ? defaultExecutorType : executorType; 3 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 4 Executor executor; 5 if (ExecutorType.BATCH == executorType) { 6 executor = new BatchExecutor(this, transaction); 7 } else if (ExecutorType.REUSE == executorType) { 8 executor = new ReuseExecutor(this, transaction); 9 } else { 10 executor = new SimpleExecutor(this, transaction); 11 } 12 if (cacheEnabled) { 13 executor = new CachingExecutor(executor); 14 } 15 executor = (Executor) interceptorChain.pluginAll(executor); // @1 16 return executor; 17}
代碼@1,:使用InterceptorChain.pluginAll(executor)進行拆件化處理。ide
思考:使用該方法調用後,會返回一個什麼對象呢?如何自定義拆件,自定義插件如何執行呢?函數
那接下來咱們帶着上述疑問,從InterceptorChain類開始進行深刻學習。
從名字上看其大意爲攔截器鏈。
1public Object proceed() throws InvocationTargetException, IllegalAccessException { 2 return method.invoke(target, args); 3}
該方法的主要目的就是進行處理鏈的傳播,執行完攔截器的方法後,最終須要調用目標方法的invoke方法。
記下來中先重點分析一下InterceptorChain方法的pluginAll方法,由於從開頭也知道,Mybatis在建立對象時,是調用該方法,完成目標對象的包裝。
1public Object pluginAll(Object target) { // @1 2 for (Interceptor interceptor : interceptors) { // @2 3 target = interceptor.plugin(target); 4 // @3 5 } 6 return target; 7}
代碼@1:目標對象,須要被代理的對象。
代碼@2:遍歷InterceptorChain的攔截器鏈,分別調用Intercpetor對象的Plugin進行攔截(@3)。
那接下來有三個疑問?
問題1:InterceptorChain中的interceptors是從何時初始化的呢,即攔截鏈中的攔截器從何而來。
問題2:從前面也得知,不管是建立Executor,仍是建立StatementHandler等,都是調用InterceptorChain#pluginAll方法,那是否是攔截器中的攔截器都會做用與目標對象,這應該是有問題的,該如何處理?
問題3:代理對象是如何建立的。
1public void addInterceptor(Interceptor interceptor) { 2 interceptors.add(interceptor); 3}
要想知道interceptors是如何初始化的,咱們只須要查看該方法的調用鏈便可。
一路跟蹤到源頭,咱們會發如今初始化SqlSessionFactory時,會解析一個標籤plugin,就能夠得知,會在SqlSessionFacotry的一個屬性中配置全部的攔截器。
具體配置示例以下:
1<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 2 <property name="dataSource" ref="shardingDataSource"/> 3 <property name="mapperLocations" value="classpath*:META-INF/mybatis/mappers/OrderMapper.xml"/> 4 5 <property name="plugins"> 6 <array> 7 <bean id = "teneantInteceptor" class="com.demo.inteceptor.TenaInteceptor"></bean> 8 </array> 9 </property> 10</bean>
問題1已經解決。但後面兩個問題彷佛沒有什麼突破口。因爲目前所涉及的三個類,顯然不足以給咱們提供答案,咱們先將目光移到InterceptorChain所在包中的其餘類,看看其餘類的職責如何。
在org.apache.ibatis.plugin中存在以下兩個註解類:Intercepts與Signature,從字面意思就是用來配置攔截的方法信息。
但另一個類型Plugin類確引發了個人注意。接下來咱們將重點分析Plugin方法。
其中InvocationHandler爲JDK的動態代理機制中的事件執行器,咱們能夠隱約閾值代理對象的生成將基於JDK內置的動態代理機制。
Plugin的核心屬性以下:
1private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { 2 this.target = target; 3 this.interceptor = interceptor; 4 this.signatureMap = signatureMap; 5 }
注意:其構造函數爲私有的,那如何構建Plugin呢,其構造方法爲Plugin的鏡頭方法wrap中被調用。
1public static Object wrap(Object target, Interceptor interceptor) { 2 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // @1 3 Class<?> type = target.getClass(); 4 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // @2 5 if (interfaces.length > 0) { 6 return Proxy.newProxyInstance( // @3 7 type.getClassLoader(), 8 interfaces, 9 new Plugin(target, interceptor, signatureMap)); 10 } 11 return target; 12}
代碼@1:獲取待包裝的Interceptor的方法簽名映射表,稍後詳細分析。
代碼@2:獲取須要代理的對象的Class上聲明的全部接口。
代碼@3:使用JDK內置的Proxy建立代理對象。Proxy建立代理對象的方法聲明以下:
1public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h), 2
注意其事件處理器爲Plugin,故在動態運行過程當中會執行Plugin的invoker方法。
在進入Plugin#invoker方法學習以前,咱們先重點查看一下getSignatureMap、getAllInterfaces的實現。
1private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { 2 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // @1 3 if (interceptsAnnotation == null) { // issue #251 // @2 4 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); 5 } 6 Signature[] sigs = interceptsAnnotation.value(); // @3 7 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); 8 for (Signature sig : sigs) { 9 Set<Method> methods = signatureMap.get(sig.type()); 10 if (methods == null) { 11 methods = new HashSet<Method>(); 12 signatureMap.put(sig.type(), methods); 13 } 14 try { 15 Method method = sig.type().getMethod(sig.method(), sig.args()); 16 methods.add(method); 17 } catch (NoSuchMethodException e) { 18 throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); 19 } 20 } 21 return signatureMap; 22}
代碼@1:首先從Interceptor的類上獲取Intercepts註解。
代碼@2:若是Interceptor的類上沒有定義Intercepts註解,則拋出異常,說明咱們在自定義插件時,必需要有Intercepts註解。
代碼@3:解析Interceptor的values屬性(Signature[])數組,而後存入HashMap, Set< Method>>容器內。
舒適提示:從這裏能夠得知:自定義的插件必須定義Intercepts註解,其註解的value值爲Signature。
1private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { 2 Set<Class<?>> interfaces = new HashSet<Class<?>>(); 3 while (type != null) { 4 for (Class<?> c : type.getInterfaces()) { 5 if (signatureMap.containsKey(c)) { 6 interfaces.add(c); 7 } 8 } 9 type = type.getSuperclass(); 10 } 11 return interfaces.toArray(new Class<?>[interfaces.size()]); 12}
該方法的實現比較簡單,並非獲取目標對象所實現的全部接口,而是返回須要攔截的方法所包括的接口。
1public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // @1 2 try { 3 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 4 if (methods != null && methods.contains(method)) { // @2 5 return interceptor.intercept(new Invocation(target, method, args)); // @3 6 } 7 return method.invoke(target, args); // @4 8 } catch (Exception e) { 9 throw ExceptionUtil.unwrapThrowable(e); 10 } 11}
代碼@1:首先對其參數列表作一個簡單的說明:
代碼@2:獲取當前執行方法所屬的類,並獲取須要被攔截的方法集合。
代碼@3:若是需被攔截的方法集合包含當前執行的方法,則執行攔截器的interceptor方法。
代碼@4:若是不是,則直接調用目標方法的Invoke方法。
從該方法能夠看出Interceptor接口的intercept方法就是攔截器自身須要實現的邏輯,其參數爲Invocation,在該方法的結束,須要調用invocation#proceed()方法,進行攔截器鏈的傳播。
從目前的學習中,咱們已經瞭解了Plugin.wrap方法就是生成帶來帶來類的惟一入口,那該方法在什麼地方調用呢?從代碼類庫中沒有找到該方法的調用鏈,說明該方法是供用戶調用的。
再看InterceptorChain方法的pluginAll方法:
1public Object pluginAll(Object target) { // @1 2 for (Interceptor interceptor : interceptors) { // @2 3 target = interceptor.plugin(target); // @3 4 } 5 return target; 6}
該方法會遍歷用戶定義的插件實現類(Interceptor),並調用Interceptor的plugin方法,對target進行拆件化處理,即咱們在實現自定義的Interceptor方法時,在plugin中須要根據本身的邏輯,對目標對象進行包裝(代理),建立代理對象,那咱們就能夠在該方法中使用Plugin#wrap來建立代理類。
接下來咱們再來用序列圖來對上述源碼分析作一個總結:
看到這裏,你們是否對上面提出的3個問題都已經有了本身的答案了。
問題1:InterceptorChain中的interceptors是從何時初始化的呢,即攔截鏈中的攔截器從何而來。
答:在初始化SqlSesstionFactory的時候,會解析屬性plugins屬性,會加載全部的攔截器到InterceptorChain中。
問題2:從前面也得知,不管是建立Executor,仍是建立StatementHandler等,都是調用InterceptorChain#pluginAll方法,那是否是攔截器中的攔截器都會做用與目標對象,這應該是有問題的,該如何處理?
答案是在各自訂閱的Interceptor#plugin方法中,咱們能夠根據傳入的目標對象,是不是該攔截器關注的,若是不關注,則直接返回目標對象,若是關注,則使用Plugin#wrap方法建立代理對象。
問題3:代理對象是如何建立的?
代理對象是使用JDK的動態代理機制建立,使用Plugin#wrap方法建立。
實踐是檢驗真理的惟一標準,那到底如何使用Mybatis的插件機制呢?
建立自定義的攔截器Interceptor,實現Interceptor接口。
http://www.javashuo.com/article/p-gdccefpe-mh.html
更多文章請關注微信公衆號:
一波廣告來襲,做者新書《RocketMQ技術內幕》已出版上市:《RocketMQ技術內幕》已出版上市,目前可在主流購物平臺(京東、天貓等)購買,本書從源碼角度深度分析了RocketMQ NameServer、消息發送、消息存儲、消息消費、消息過濾、主從同步HA、事務消息;在實戰篇重點介紹了RocketMQ運維管理界面與當前支持的39個運維命令;並在附錄部分羅列了RocketMQ幾乎全部的配置參數。本書獲得了RocketMQ創始人、阿里巴巴Messaging開源技術負責人、Linux OpenMessaging 主席的高度承認並做序推薦。目前是國內第一本成體系剖析RocketMQ的書籍。