Dubbo SPI机制学习总结(持续更新...)
參考文章:Dubbo的SPI機制分析
首先來看看 Java SPI 的機制
Java SPI 起初是提供給廠商做插件開發(fā)用的,例如數(shù)據(jù)庫驅(qū)動java.sql.Driver,市面上各種各樣的數(shù)據(jù)庫,不同的數(shù)據(jù)庫底層協(xié)議都不一樣,為了方便開發(fā)者調(diào)用數(shù)據(jù)庫而不用關(guān)心它們之間的差異,因此必須提供一個統(tǒng)一的接口來規(guī)范和約束這些數(shù)據(jù)庫。有了統(tǒng)一的接口,數(shù)據(jù)庫廠商就可以按照規(guī)范去開發(fā)自己的數(shù)據(jù)庫驅(qū)動了。
廠商開發(fā)好數(shù)據(jù)庫驅(qū)動了,應(yīng)用如何使用呢?該使用哪個驅(qū)動呢?以 MySQL 為例,早期手寫 JDBC 時,開發(fā)者需要手動注冊驅(qū)動,現(xiàn)在已經(jīng)不需要了,就是利用了 SPI 機制。
Java SPI 使用了策略模式,一個接口多種實現(xiàn),開發(fā)者面向接口編程,具體的實現(xiàn)并不在程序中直接硬編碼,而是通過外部文件進行配置。
Java SPI 約定了一個規(guī)范,使用步驟如下:
- 編寫一個接口。
- 編寫具體實現(xiàn)類。
- 在 ClassPath 下的META-INF/services,目錄創(chuàng)建以接口全限定名命名的文件,文件內(nèi)容為實現(xiàn)類的全限定名,多個實現(xiàn)用換行符分割。
- 通過 ServiceLoader 類獲取具體實現(xiàn)。
接口:
實現(xiàn)類:
簡單實例
package org.sun.spi.services;/*** 這是java 的 spi, 沒有@SPI注解, 類似 mySQL*/ public interface Say {void say(); }實現(xiàn)類1
package org.sun.spi.impl;import org.sun.spi.services.Say;public class SayImpl implements Say {@Overridepublic void say() {System.out.println("nihao .....");} }實現(xiàn)類2
package org.sun.spi.impl;import org.sun.spi.services.Say;public class SayWrapper implements Say {@Overridepublic void say() {System.out.println("hello SayWrapper。。。。");} }META-INF/services 文件
測試:
public class Main {public static void main(String[] args) {ServiceLoader<Say> serviceLoader = ServiceLoader.load(Say.class);serviceLoader.forEach(say -> say.say());}結(jié)果:
nihao ..... hello SayWrapper。。。。Dubbo SPI機制
Dubbo SPI 定義了一套自己的規(guī)范,同時對 Java SPI 存在的問題進行了改進,優(yōu)點如下:
- 擴展類按需加載,節(jié)約資源。
- SPI 文件采用 Key=Value 形式,可以根據(jù)擴展名靈活獲取實現(xiàn)類。
- 擴展類對象做了緩存,避免重復(fù)創(chuàng)建。
- 擴展類加載失敗有詳細日志,方便排查。 支持 AOP 和 IOC。
Dubbo SPI 使用規(guī)范:
- 編寫接口,接口必須加@SPI 注解,代表它是一個可擴展的接口。
- 編寫實現(xiàn)類。
- 在 ClassPath 下的META-INF/dubbo,目錄創(chuàng)建以接口全限定名命名的文件,文件內(nèi)容為 Key=Value 格式,Key 是擴展點的名稱,Value 是擴展點實現(xiàn)類的全限定名。
- 通過 ExtensionLoader 類獲取擴展點實現(xiàn)。
Dubbo 默認會掃描META-INF/services
、META-INF/dubbo
、META-INF/dubbo/internal
三個目錄下的配置,第一個是為了兼容 Java SPI,第三個是 Dubbo 內(nèi)部使用的擴展點。
測試用例
// TODO源碼分析:
ExtensionLoader.getExtensionLoader
getExtension()
public T getExtension(String name, boolean wrap) {checkDestroyed();if (StringUtils.isEmpty(name)) {throw new IllegalArgumentException("Extension name == null");}// 如果 name 為true,則返回一個默認的擴展點if ("true".equals(name)) {return getDefaultExtension();}String cacheKey = name;if (!wrap) {cacheKey += "_origin";}// 創(chuàng)建或者返回一個holder對象, 用于緩存擴展類的實例final Holder<Object> holder = getOrCreateHolder(cacheKey);Object instance = holder.get();// 如果緩存不存在則創(chuàng)建一個實例if (instance == null) {// 同步設(shè)置,懶漢模式synchronized (holder) {instance = holder.get();if (instance == null) {instance = createExtension(name, wrap);holder.set(instance);}}}return (T) instance;PS : 有意思的類,學(xué)習(xí)
/*** 持有一個泛型* Helper Class for hold a value.*/ public class Holder<T> {// 保證線程可見性private volatile T value;public void set(T value) {this.value = value;}public T get() {return value;}}上述代碼就是先查緩存,如果未命中,則創(chuàng)建一個擴展對象,createExtension() 應(yīng)該就是去指定的路徑下查找name 對應(yīng)的擴展點實現(xiàn),并且實例化之后返回。
@SuppressWarnings("unchecked")private T createExtension(String name, boolean wrap) {// 根據(jù) name 返回擴展類Class<?> clazz = getExtensionClasses().get(name);if (clazz == null || unacceptableExceptions.contains(name)) {throw findException(name);}try {// 從緩存中查找該類是否已經(jīng)初始化// ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);T instance = (T) extensionInstances.get(clazz);if (instance == null) {// 如果沒有,則重新創(chuàng)建一個實例并加入緩存, 反射機制extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));instance = (T) extensionInstances.get(clazz);// 初始化前的相關(guān)操作instance = postProcessBeforeInitialization(instance, name);// 依賴注入injectExtension(instance);instance = postProcessAfterInitialization(instance, name);}if (wrap) {// 通過 Wrapper 進行包裝List<Class<?>> wrapperClassesList = new ArrayList<>();if (cachedWrapperClasses != null) {wrapperClassesList.addAll(cachedWrapperClasses);wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}if (CollectionUtils.isNotEmpty(wrapperClassesList)) {for (Class<?> wrapperClass : wrapperClassesList) {Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);boolean match = (wrapper == null) ||((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&!ArrayUtils.contains(wrapper.mismatches(), name));if (match) {instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));instance = postProcessAfterInitialization(instance, name);}}}}// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.initExtension(instance);return instance;} catch (Throwable t) {throw new IllegalStateException("Extension instance (name: " + name + ", class: " +type + ") couldn't be instantiated: " + t.getMessage(), t);}}繼續(xù)跟蹤getExtensionClasses
- 從緩存中獲取已經(jīng)被加載的擴展類
- 如果未命中緩存, 則調(diào)用loadExtensionClasses 加載擴展類
loadExtensionClasses()
/*** synchronized in getExtensionClasses*/private Map<String, Class<?>> loadExtensionClasses() {checkDestroyed();// 獲得當(dāng)前type擴展接口的默認擴展對象, 并且緩存cacheDefaultExtensionName();Map<String, Class<?>> extensionClasses = new HashMap<>();for (LoadingStrategy strategy : strategies) {// 加載指定文件目錄下的配置文件loadDirectory(extensionClasses, strategy, type.getName());// compatible with old ExtensionFactoryif (this.type == ExtensionInjector.class) {loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());}}return extensionClasses;}(精彩后續(xù),請看下回分解)
總結(jié)
以上是生活随笔為你收集整理的Dubbo SPI机制学习总结(持续更新...)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CDQ分治入门 + 例题 Arnooks
- 下一篇: 如何在SQL Server中附加Pubs