咱們都是知道一個合格的開源框架對於擴展的支持都要是至關彈性的,Dubbo 也不例外。Dubbo採用微內核+插件體系,使得設計優雅,擴展性強。Dubbo的擴展機制是基於SPI思想來實現的,可是並無採用JDK中原生的SPI機制。java
1.什麼是SPIredis
java spi的具體約定爲:當服務的提供者,提供了服務接口的一種實現以後,在jar包的META-INF/services/目錄裏同時建立一個以服務接口命名的文件。該文件裏就是實現該服務接口的具體實現類。而當外部程序裝配這個模塊的時候,就能經過該jar包META-INF/services/裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。 基於這樣一個約定就能很好的找到服務接口的實現類,而不須要再代碼裏制定。jdk提供服務實現查找的一個工具類:java.util.ServiceLoader。架構
2.爲何Dubbo本身實現SPIapp
JDK中SPI具備很大的缺點,JDK中標準的SPI會一次性實例化擴展點全部的實現,無論這些實例化出來的擴展點實現有沒有被用到。有的擴展點實現初始化時很是的耗時,即便沒有用到也會被加載,這樣就很浪費資源。
Dubbo的SPI機制中增長了對擴展點IOC和AOP的支持,一個擴展點能夠直接setter注入到其餘的擴展點中。框架
3.Dubbo中擴展點的概念ide
Dubbo做用靈活的框架,並不會強制全部用戶都必定使用Dubbo提供的某些架構。例如註冊中心(Registry),Dubbo提供了zk和redis,可是若是咱們更傾向於其餘的註冊中心的話,咱們能夠替換掉Dubbo提供的註冊中心。針對這種可被替換的技術實現點咱們稱之爲擴展點,相似的擴展點有不少,例如Protocol,Filter,Loadbalance等等。函數
4.微內核架構工具
微內核架構 (Microkernel architecture) 模式也被稱爲插件架構 (Plugin architecture) 模式。本來與內核集成在一塊兒的組件會被分離出來,內核提供了特定的接口使得這些組件能夠靈活的接入,這些組件在內核的管理下工做,可是這些組件能夠獨立的發展、更改(不會對現有系統形成改動),只要符合內核的接口便可。典型的例子好比,Eclipse,IDEA 。學習
5.Dubbo 的微內核設計編碼
Dubbo內核對外暴露出擴展點,經過擴展點能夠實現定製的符合本身業務需求的功能。Dubbo內核經過ExtensionLoader擴展點加載器來加載各個SPI擴展點。Dubbo 內核對擴展是無感的 ,徹底不知道擴展的存在 ,內核代碼中不會出現使用具體擴展的硬編碼。
術語說明 :
SPI : Service Provider Interface 。
擴展點 : 稱 Dubbo 中被 @SPI 註解的 Interface 爲一個擴展點。
擴展 : 被 @SPI 註解的 Interface 的實現稱爲這個擴展點的一個擴展。
6.Dubbo中SPI的約定
擴展點約定 : 擴展點必須是 Interface 類型 ,必須被 @SPI 註解 ,知足這兩點纔是一個擴展點。
擴展定義約定:在 META-INF/services/$擴展點接口的全類名,META-INF/dubbo/$擴展點接口的全類名 , META-INF/dubbo/internal/$擴展點接口的全類名 , 這些路徑下定義的文件名稱爲 $擴展點接口的全類名 , 文件中以鍵值對的方式配置擴展點的擴展實現。
默認適應擴展 :被 @SPI("abc") 註解的 Interface , 那麼這個擴展點的缺省適應擴展就是 SPI 配置文件中 key 爲 "abc" 的擴展。
7.接口實現類配置
經過註解@SPI("dubbo"),默認使用Protocol的實現類DubboProtocol,其實dubbo和實現類DubboProtocol關係相似Spring配置文件中的id和class的關係,不一樣的是Dubbo的關係是
配置在目錄/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中,文件內容爲:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
簡單來講其實現機制就相似Spring的bean注入,經過key(dubbo、http、hessian)來找到其實現類。
8.擴展加載器 ExtensionLoader
擴展加載器絕對是一個核心組件了 ,它控制着 dubbo 內部全部擴展點的初始化、加載擴展的過程。這個類的源碼是頗有必要深刻學習的。從 Dubbo 內核設計簡圖能夠看到,如今的學習尚未接觸到 dubbo 的內核。
public class ExtensionLoader<T> { private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>(); // ============================== private final Class<?> type; private final ExtensionFactory objectFactory; private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>(); private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>(); private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); private volatile Class<?> cachedAdaptiveClass = null; private String cachedDefaultName; private volatile Throwable createAdaptiveInstanceError; private Set<Class<?>> cachedWrapperClasses; private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
ExtensionLoader 中會存儲兩個靜態屬性 , EXTENSION_LOADERS 保存了內核開放的擴展點對應的 ExtensionLoader 實例對象 (說明了一種擴展點有一個對應的 ExtensionLoader 對象)。EXTENSION_INSTANCES 保存了擴展類型 (Class) 和擴展類型的實例對象。
type : 被 @SPI 註解的 Interface , 也就是擴展點。
objectFactory : 擴展工廠,能夠從中獲取到擴展類型實例對象 ,缺省爲 AdaptiveExtensionFactory。
cachedNames : 保存不知足裝飾模式(不存在只有一個參數,而且參數是擴展點類型實例對象的構造函數)的擴展的名稱。
cachedClasses : 保存不知足裝飾模式的擴展的 Class 實例 , 擴展的名稱做爲 key , Class 實例做爲 value。
cachedActivates : 保存不知足裝飾模式 , 被 @Activate 註解的擴展的 Class 實例。
cachedAdaptiveClass : 被 @Adpative 註解的擴展的 Class 實例 。
cachedInstances : 保存擴展的名稱和實例對象 , 擴展名稱爲 key , 擴展實例爲 value。
cachedDefaultName : 擴展點上 @SPI 註解指定的缺省適配擴展。
createAdaptiveInstanceError : 建立適配擴展實例過程當中拋出的異常。
cachedWrapperClasses : 知足裝飾模式的擴展的 Class 實例。
exceptions : 保存在加載擴展點配置文件時,加載擴展點過程當中拋出的異常 , key 是當前讀取的擴展點配置文件的一行 , value 是拋出的異常
9.SPI配置文件讀取
咱們已經知道Dubbo將Protocol的實現類配置到/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol配置文件中,接下來咱們要看的就是將配置文件中的對應關係解析出來了。其處理操做是在ExtensionLoder的loadFile方法中類來實現的,
簡單來講讀取dubbo=com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory,獲取到鍵dubbo,初始化值com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory而後將對應關係保存到一個Map中,這樣就能夠根據key找到實現類了。
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL url = urls.nextElement(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); try { String line = null; while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { Class<?> clazz = Class.forName(line, true, classLoader); if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { try { clazz.getConstructor(type); Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } catch (NoSuchMethodException e) { clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } for (String n : names) { if (!cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } } } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } // end of while read lines } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url + ") in " + url, t); } } // end of while urls } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } }