關於 java spi 的介紹能夠參見下面這個帖子java
http://singleant.iteye.com/blog/1497259緩存
Dubbo的擴展點加載從JDK標準的SPI(Service Provider Interface)擴展點發現機制增強而來。ruby
Dubbo改進了JDK標準的SPI的如下問題:app
在擴展類的jar包內,放置擴展點配置文件:META-INF/dubbo/接口全限定名,內容爲:配置名=擴展實現類全限定名,多個實現類用換行符分隔。ide
示例:函數
在協議的實現jar包內放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,內容爲:url
xxx=com.alibaba.xxx.XxxProtocolspa
實現類內容:代理
package com.alibaba.xxx; import com.alibaba.dubbo.rpc.Protocol; public class XxxProtocol implemenets Protocol { // ... }
加載擴展實現, 在dubbo中都是經過ExtensionLoader實現的, 代碼如:code
ExtensionLoader.getExtensionLoader(Container.class).getExtension("registry");
咱們以方法getExtensionLoader做爲入口來分析加載的實現:
public static<T> ExtensionLoader<T> getExtensionLoader(Class<T> type){ if(type==null) throw new IllegalArgumentException("Extensiontype == null"); if(!type.isInterface()){ throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)){ thrownewIllegalArgumentException("Extensiontype("+type+ ") is not extension, because WITHOUT@" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader<T> loader=(ExtensionLoader<T>)EXTENSION_LOADERS.get(type); if(loader==null){ EXTENSION_LOADERS.putIfAbsent(type,newExtensionLoader<T>(type)); loader=(ExtensionLoader<T>)EXTENSION_LOADERS.get(type); } return loader; }
此方法根據擴展類型, 獲得一個擴展加載器(ExtensionLoader)
1, 經過方法開始部分的幾個校驗能夠知道:
· 擴展類泛型類型不能爲空
· 必須是一個接口.
· 此接口必須打上@SPI註解
2, 先從EXTENSION_LOADERS緩存裏取, 取不到會實例化一個ExtensionLoader, 而且緩存起來;
在ExtensionLoader的構造器中, 會經過AdaptiveExtension方式獲得一個ExtensionFactory; AdaptiveExtension及ExtensionFactory後面會再講到.
3,獲得ExtensionLoader以後, 再來看看getExtension(String name)方法
邏輯比較簡單,
· 若是name 傳一個 "true", 表示取默認擴展; getDefaultExtension(), 實現邏輯見4.
· 先從緩存cachedInstances裏取, 取不到經過createExtension方法建立, 完了再放緩存.
4, 獲取默認擴展的實現邏輯.getDefaultExtension
5, createExtension方法:
1) 經過getExtensionClasses方法, 取得全部的擴展類緩存, 並從中取得name對應的class. getExtensionClasses實現邏輯見6.
· 根據類型, 從EXTENSION_INSTANCES得到緩存的實例.
· 若是不存在, 則經過class.newInstance建立實例, 並緩存.
· 經過injectExtension方法, 給這個實例注入各類屬性.injectExtension實現邏輯, 詳見7.
· 若是待擴展的T類型,有Wrapper包裝器類(構造函數有T入參),實例化,而且injectExtension:
Set<Class<?>> wrapperClasses = cachedWrapperClasses; if(wrapperClasses != null && wrapperClasses.size() > 0){ for(Class<?> wrapperClass : wrapperClasses){ instance = injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance)); } } return instance;
能夠到看最後返回的是最後一個包裝器類;以前的包裝器類以及最開始最純粹的擴展類實例, 只是調用了一下構造器方法, 而後傳遞給後面的包裝器類; 這樣就能在各級包裝器類中添加各類擴展屬性級擴展方法(裝飾器模式).
包裝類在loadFile加載擴展類的時候, 加載到cachedWrapperClasses緩存的.(loadFile實現邏輯詳見8)
6.getExtensionClasses 獲取全部的擴展類, 實現邏輯
1) 從cachedClasses緩存裏取, 若是不存在, 經過loadExtensionClasses方法加載.
2) loadExtensionClasses實現邏輯:
a.經過getExtensionLoader(Class<T>type)初始傳進來的type類型上的SPI註解的value值, 解析獲得一個cachedDefaultName(這個就是默認擴展了.
b.經過loadFile方法(實現邏輯見8), 加載DUBBO_INTERNAL_DIRECTORY,DUBBO_DIRECTORY,SERVICES_DIRECTORY三個目錄下的擴展類彙總到extensionClasses緩存, 最後一塊兒返回.
7.injectExtension方法,給實例注入屬性的實現邏輯.
經過反射, 找到只有一個入參的set public方法, 獲得參數類型.
經過ExtensionFactory獲得這個屬性的擴展實例, 若是存在的話, 就注入.
8, loadFile 經過配置文件加載擴展類的實現邏輯:
1)得到指定擴展配置文件名, 如:META-INF/dubbo/internal/com.alibaba.dubbo.common.compiler.Compiler
2)經過類加載器獲得配置文件資源Url, 在類路徑下可能會找到多個.
3)遍歷, 讀取配置文件中的每一行, 並解析, 配置行形如:javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
4)經過class.forName將等號後面的類, 轉爲類類型.
5)校驗:
a.必須是T接口的實現類 type.isAssignableFrom(clazz)
b. 若是這個類有@Adaptive註解. 而且緩存到cachedAdaptiveClass, 若是cachedAdaptiveClass已經有了, 但不是當前解析獲得的class, 說明該接口有多個@Adaptive註解擴展類, 報錯.關於@Adaptive後面會再分析.
c.若是不是@Adaptive擴展類, 嘗試得到該類帶有該類類型爲入參的構造函數
clazz.getConstructor(type);
存在, 則認爲是包裝器類, 加入到包裝器緩存.
d.若是不是包裝器類,
若是配置行, 僅有類名, 沒有=號及等號左邊的內容, 看看這個類上有沒有@Extension註解, 而且註解的值與類名
若是仍是沒有: 若是擴展類的類名是接口類類名結尾.
clazz.getSimpleName().length()>type.getSimpleName().length()
&&clazz.getSimpleName().endsWith(type.getSimpleName()
那麼 擴展名就是去掉接口名以後, 前半部分.
不然報錯.
這個擴展名, 容許逗號隔開, 配置多個
從擴展類上獲取@Activate註解, 若是有, 存入cachedActivates緩存, 若是擴展名有多個, 只以第一個做爲緩存key.
遍歷每個擴展名, 放入緩存, 名稱擴展類對應擴展名.同時放入extensionClasses緩存,擴展名對應擴展類.
ExtensionLoader 還可加載Activate類 getActivateExtension()
Adaptive實例,直到擴展點方法執行時才決定調用是一個擴展點實現。
擴展點方法調用會有URL參數(或是參數有URL成員)
這樣依賴的擴展點也能夠從URL拿到配置信息,全部的擴展點本身定好配置的Key後,配置信息從URL上從最外層傳入。
Adaptive實例的邏輯是固定,指定提取的URL的Key,便可以代理真正的實現類上,能夠動態生成。
在Dubbo的ExtensionLoader的擴展點類開對應的Adaptive實現是在加載擴展點裏動態生成。指定提取的URL的Key經過@Adaptive註解在接口方法上提供。
下面是Dubbo的Transporter擴展點的代碼:
public interface Transporter { @Adaptive({"server", "transport"}) Server bind(URL url, ChannelHandler handler) throws RemotingException; @Adaptive({"client", "transport"}) Client connect(URL url, ChannelHandler handler) throws RemotingException; }
對於bind方法表示,Adaptive實現先查找"server"key,若是該Key沒有值則找"transport"key值,來決定代理到哪一個實際擴展點。
調用擴展以下面的代碼:
Protocol refprotocol= ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
下面是getAdaptiveExtension()方法的實現邏輯:
1, 從cachedAdaptiveInstance緩存獲取Adaptive類實例, 若是不存在, 調用createAdaptiveExtension()方法建立, 並緩存
2, createAdaptiveExtension方法, 經過getAdaptiveExtensionClass()獲得Adaptive類並實例化, 而且經過injectExtension方法注入各屬性.
3, getAdaptiveExtensionClass()實現邏輯: 從緩存cachedAdaptiveClass中獲取, 取不到調用createAdaptiveExtensionClass方法建立
4, 經過createAdaptiveExtensionClassCode方法生成@Adaptive類字節碼, 而且經過Compiler類編譯獲得, Compiler也是經過ExtensionLoader getAdaptiveExtension獲得的.
這裏不會遞歸調用棧溢出嗎?
因爲在getExtensionClasses()中已經找了cachedAdaptiveClass(見8.5.b), 因此若是cachedAdaptiveClass仍然爲空, 會去createAdaptiveExtensionClass() , 這樣就會調用堆棧溢出. 這樣就不難理解爲啥8.5.b中, 若是找到多個@Adaptive註解的類會報異常.
Compiler有一個實現類加了@Adaptive註解, 因此不會再去createAdaptiveExtensionClass.
AdaptiveCompiler
@Adaptive
public class AdaptiveCompilerimplementsCompiler