Dubbo SPI机制学习总结(持续更新...)
參考文章:Dubbo的SPI機制分析
首先來看看 Java SPI 的機制
Java SPI 起初是提供給廠商做插件開發用的,例如數據庫驅動java.sql.Driver,市面上各種各樣的數據庫,不同的數據庫底層協議都不一樣,為了方便開發者調用數據庫而不用關心它們之間的差異,因此必須提供一個統一的接口來規范和約束這些數據庫。有了統一的接口,數據庫廠商就可以按照規范去開發自己的數據庫驅動了。
廠商開發好數據庫驅動了,應用如何使用呢?該使用哪個驅動呢?以 MySQL 為例,早期手寫 JDBC 時,開發者需要手動注冊驅動,現在已經不需要了,就是利用了 SPI 機制。
Java SPI 使用了策略模式,一個接口多種實現,開發者面向接口編程,具體的實現并不在程序中直接硬編碼,而是通過外部文件進行配置。
Java SPI 約定了一個規范,使用步驟如下:
- 編寫一個接口。
- 編寫具體實現類。
- 在 ClassPath 下的META-INF/services,目錄創建以接口全限定名命名的文件,文件內容為實現類的全限定名,多個實現用換行符分割。
- 通過 ServiceLoader 類獲取具體實現。
接口:
實現類:
簡單實例
package org.sun.spi.services;/*** 這是java 的 spi, 沒有@SPI注解, 類似 mySQL*/ public interface Say {void say(); }實現類1
package org.sun.spi.impl;import org.sun.spi.services.Say;public class SayImpl implements Say {@Overridepublic void say() {System.out.println("nihao .....");} }實現類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());}結果:
nihao ..... hello SayWrapper。。。。Dubbo SPI機制
Dubbo SPI 定義了一套自己的規范,同時對 Java SPI 存在的問題進行了改進,優點如下:
- 擴展類按需加載,節約資源。
- SPI 文件采用 Key=Value 形式,可以根據擴展名靈活獲取實現類。
- 擴展類對象做了緩存,避免重復創建。
- 擴展類加載失敗有詳細日志,方便排查。 支持 AOP 和 IOC。
Dubbo SPI 使用規范:
- 編寫接口,接口必須加@SPI 注解,代表它是一個可擴展的接口。
- 編寫實現類。
- 在 ClassPath 下的META-INF/dubbo,目錄創建以接口全限定名命名的文件,文件內容為 Key=Value 格式,Key 是擴展點的名稱,Value 是擴展點實現類的全限定名。
- 通過 ExtensionLoader 類獲取擴展點實現。
Dubbo 默認會掃描META-INF/services
、META-INF/dubbo
、META-INF/dubbo/internal
三個目錄下的配置,第一個是為了兼容 Java SPI,第三個是 Dubbo 內部使用的擴展點。
測試用例
// 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";}// 創建或者返回一個holder對象, 用于緩存擴展類的實例final Holder<Object> holder = getOrCreateHolder(cacheKey);Object instance = holder.get();// 如果緩存不存在則創建一個實例if (instance == null) {// 同步設置,懶漢模式synchronized (holder) {instance = holder.get();if (instance == null) {instance = createExtension(name, wrap);holder.set(instance);}}}return (T) instance;PS : 有意思的類,學習
/*** 持有一個泛型* 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;}}上述代碼就是先查緩存,如果未命中,則創建一個擴展對象,createExtension() 應該就是去指定的路徑下查找name 對應的擴展點實現,并且實例化之后返回。
@SuppressWarnings("unchecked")private T createExtension(String name, boolean wrap) {// 根據 name 返回擴展類Class<?> clazz = getExtensionClasses().get(name);if (clazz == null || unacceptableExceptions.contains(name)) {throw findException(name);}try {// 從緩存中查找該類是否已經初始化// ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);T instance = (T) extensionInstances.get(clazz);if (instance == null) {// 如果沒有,則重新創建一個實例并加入緩存, 反射機制extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));instance = (T) extensionInstances.get(clazz);// 初始化前的相關操作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);}}繼續跟蹤getExtensionClasses
- 從緩存中獲取已經被加載的擴展類
- 如果未命中緩存, 則調用loadExtensionClasses 加載擴展類
loadExtensionClasses()
/*** synchronized in getExtensionClasses*/private Map<String, Class<?>> loadExtensionClasses() {checkDestroyed();// 獲得當前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;}(精彩后續,請看下回分解)
總結
以上是生活随笔為你收集整理的Dubbo SPI机制学习总结(持续更新...)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CDQ分治入门 + 例题 Arnooks
- 下一篇: 写给那些傻傻的,想做服务器开发的应届生