上一個章節說道Dubbo的微內核插件式開發其實就是在SPI設計思路上作了優化升級。咱們都知道SPI設計思路是當咱們要獲取某個接口的實現時,使用ServiceLoader去掃描第三方依賴JAR包的META-INF/services/目錄,若是找到名字爲這個接口全路徑的文件,那麼就會讀取這個文件中的內容,而後SPI約定這個內容就是實現類的全路徑名稱,因此ServiceLoader就能夠根據這個實現類路徑來實例化提供服務。而Dubbo是本身實現了加載器,名字叫ExtensionLoader。Dubbo約定只有標記了@SPI註解的接口,才能使用ExtensionLoader去加載實現類。ExtensionLoader會依次掃描META-INF/dubbo/internal/目錄、META-INF/dubbo/目錄、META-INF/services/目錄,掃描出名字爲接口的全路徑名的文件,而後文件內容約定key=value的形式出現,key就是這個實現類的別名,value纔是實現類的全路徑。固然當一個項目裏面,對同一個接口可能會不少種實現,那到底使用哪一種實現,因此這裏Dubbo作了特別的設計,每一個支持SPI接口會對應須要一個適配實現(能夠本身實現,也能夠系統幫你生成), 而後用戶能夠根據這個設配實現來決定最後到底須要選擇哪一個具體實現。ExtensionLoader在掃描到實現類時,並發現這個實現類上標記了@Adaptive註解時,就會把這個實現類做爲適配實現看待,若是發現掃描出來的實現類裏面一個都沒有標記這個註解,那麼ExtensionLoader會自動生成一個實現類做爲該接口的設配實現,因此全部的@SPI接口在使用時都會存在一個適配實現。java
上面說到了,只有註解了SPI註解的接口,ExtensionLoader才支持加載擴展實現,SPI有個value參數,這個value指定的是擴展實現的的別名,指定後默認使用的就是這個別名對應的擴展實現。SPI源碼以下:數組
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface SPI { String value() default ""; }
Dubbo內部的不少實現都基於這種方式來擴展實現的。咱們都知道Dubbo支持不少序列化協議,我就拿這個例子來講明,看Protocol協議接口,他標記了SPI註解,並指定了默認實現是別名叫dubbo的協議實現。因此係統裏面經過ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();獲取協議實現時,若是沒有特別指定用哪一種實現,默認就會使用別名叫dubbo的協議。緩存
@SPI("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
做用一:@Adaptive註解主要用來標記適配類實現,上面說了,ExtensionLoader在各個依賴JAR尋找實現類時,會檢查實現類有沒有打上@Adaptive標記,若是打上了,說明該實現類就是適配實現。就會緩存到ExtensionLoader的cachedAdaptiveClass變量裏面。例如我想要本身實現一個Protocol接口的適配實現,能夠這樣寫:併發
@Adaptive public class AdapterProtocol implements Protocol { @Override int getDefaultPort(){//省略...} @Override <T> Exporter<T> export(Invoker<T> invoker) throws RpcException{//省略...} @Override <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException{//省略...} @Override void destroy(){//省略...} }
而後在META-INF/services/目錄下放置文件:com.alibaba.dubbo.rpc.Protocol。裏面的內容設置成adapterProtocol=mypackage.AdapterProtocol。這樣咱們就是本身實現了一個Protocol的設配實現,當系統使用ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();去加載實現時,拿到的就是咱們實現的適配類了。ide
做用二:@Adaptive還能夠標記在接口的方法上。這種狀況是,當ExtensionLoader掃描了全部實現類以後,發現沒有一個是標記了@Adaptive的實現類。因而ExtensionLoader會使用javasist幫咱們自動生成一個設配類。在自動實現各個接口的方式時,會根據該方法上標記的@Adaptive註解的value[]數組值來進行生成。若是沒有標記@Adaptive的就拋出異常。例如上面的Protocol自動實現的類反編譯後長這樣:優化
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { //沒有打上@Adaptive的方法若是被調到拋異常 public void destroy() { throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!") } //接口中export方法打上@Adaptive註冊 public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); //參數類中要有URL屬性 if(arg0.getUrl() == null) throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); //從入參獲取統一數據模型URL com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); //從統一數據模型URL獲取協議,協議名就是SPI擴展點實現類的key if(extName == null) throw new IllegalStateException( "Fail to getextension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") usekeys([protocol])"); //利用dubbo服務查找機制根據名稱找到具體的擴展點實現 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader. getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); } //其餘方法省略... }
自動實現的設配代碼大體意思是,若是方法上沒有標記@Adaptive註解的,就拋出一個異常,若是有標記@Adaptive註解的,就判斷接口方法有沒有URL參數,若是沒有就找依附在參數中的URL,而後根據@Adaptive指定的value[]數組,合成代碼,代碼意思大體就是在使用這個適配類時,會依次使用value[]指定的實現,若是都找不到就最後使用@SPI指定的默認實現。url
關於這個註解,我簡單說一下。當咱們實現擴展時,若是加上這個註解。而後經過這個註解能夠配置一些限制條件。當系統查找擴展實現會把標記了這個註解的實現緩存起來。而後能夠在適當時候,過濾出要激活的實現。@Activate註解源碼:spa
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Activate { //group過濾條件,沒有group就不過濾 String[] group() default {}; //key過濾條件,如沒有設置,則不過濾 String[] value() default {}; //排序信息,能夠不提供 String[] before() default {}; //排序信息,能夠不提供 String[] after() default {}; //排序信息,能夠不提供 int order() default 0; }
例如,源碼中有個CacheFilter的,就使用了這個註解。插件
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY) public class CacheFilter implements Filter { //省略... }
當使用系統要使用時,這樣調用就能夠過濾出要被激活的實現設計
ExtensionLoader.getExtensionLoader(Filter.class) .getActivateExtension(url, new String[]{}, "value");