追蹤解析 Dubbo 的 Spi 機制源碼

2020-10-22 Java成長催化師

零 前期準備

0 FBI WARNING

文章異常囉嗦且繞彎。

1 版本

JDK 版本 : Adoptopenjdk 14.0.1

IDE : idea 2020.2

Dubbo 版本 : dubbo 2.7.6

2 Spi 簡介

Dubbo Spi 是 Dubbo 框架擴展性的根本基礎,是基於 jdk spi 的封裝和擴展。

3 Demo

3.1 需要擴展的接口類

import org.apache.dubbo.common.extension.Adaptive;import org.apache.dubbo.common.extension.SPI;@SPI("dubbo") // spi 最主要的註解public interface SpiDemoService { void sayHello(); }

3.2 接口實現類

一號實現類 :

public class SpiDemoOneServiceImpl implements SpiDemoService { @Override public void sayHello() { System.out.println("hello 1"); }}

二號實現類 :

public class SpiDemoTwoServiceImpl implements SpiDemoService { @Override public void sayHello() { System.out.println("hello 2"); }}

測試方法類:

import org.apache.dubbo.common.extension.ExtensionLoader;public class SpiTest { public static void main(String[] args) { // 獲取 loader 工廠類 ExtensionLoader<SpiDemoService> loader = ExtensionLoader .getExtensionLoader(SpiDemoService.class); // 獲取實體類 SpiDemoService one = loader.getExtension("one"); // 測試方法 one.sayHello(); }}

一 getExtensionLoader

回到 demo :

ExtensionLoader<SpiDemoService> loader = ExtensionLoader .getExtensionLoader(SpiDemoService.class);

1 getExtensionLoader

getExtensionLoader 是 ExtensionLoader 最核心的靜態方法,用於獲取 ExtensionLoader 實例

// org.apache.dubbo.common.extension.ExtensionLoaderpublic static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { // 可行性判斷,均忽略 if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } // 先嘗試獲取,如果獲取失敗就創建一個 ExtensionLoader // EXTENSION_LOADERS 是一個靜態 ConcurrentHashMap,用來存放 loader ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }

2 ExtensionLoader 構造器

// org.apache.dubbo.common.extension.ExtensionLoaderprivate ExtensionLoader(Class<?> type) { // 存儲要創建的 loader 的類型 this.type = type; // objectFactory 是 ExtensionFactory 類型的對象,是用來依賴注入的工廠 objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader .getExtensionLoader(ExtensionFactory.class) .getAdaptiveExtension()); }

二 getAdaptiveExtension

1 getAdaptiveExtension

// org.apache.dubbo.common.extension.ExtensionLoaderpublic T getAdaptiveExtension() { // cachedAdaptiveInstance 是一個 Holder 對象,Holder 是對 Object 的包裝 // 在此處先嘗試獲取實例化完成的對象,如果獲取不到,就進入加載邏輯 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { // 如果之前初始化的時候報錯了,那麼錯誤會被記錄下來並緩存在此處,直接拋出 // 此處的設計應該是減少鎖消耗 if (createAdaptiveInstanceError != null) { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } synchronized (cachedAdaptiveInstance) { // 雙鎖驗證 instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 創建實例並存儲在 Holder 對象裡 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { // 如果報錯了就會把錯誤存儲起來 createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } // 返回對象實例 return (T) instance;}

2 createAdaptiveExtension

創建實例對象:

// org.apache.dubbo.common.extension.ExtensionLoaderprivate T createAdaptiveExtension() { try { // getAdaptiveExtensionClass().newInstance() 方法會使用 Class 對象的 newInstance() 方法創建一個對象 // injectExtension(...) 則會對創建出來的對象進行依賴注入 return injectExtension((T)getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); }}

3 injectExtension

// org.apache.dubbo.common.extension.ExtensionLoaderprivate T injectExtension(T instance) { // objectFactory 是用來依賴注入的 ExtensionFactory // 如果 objectFactory 為空,就直接返回 // 需要注意的是,只有非 ExtensionFactory 的 loader 才有 objectFactory if (objectFactory == null) { return instance; } try { // 輪訓實例對象中所有的方法 for (Method method : instance.getClass().getMethods()) { // 如果方法不是 set 方法就跳過 // 此處可以理解為,dubbo 的 spi 依賴注入需要 set 方法支持 if (!isSetter(method)) { continue; } // 如果方法被標註了 DisableInject 註解就跳過 if (method.getAnnotation(DisableInject.class) != null) { continue; } // 如果方法的參數是原始類型就跳過 // 依賴注入需要使用包裝類型 Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { // 反射注入 String property = getSetterProperty(method); Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance;}

三 ExtensionFactory

ExtensionFactory 是用來依賴注入的工廠:

@SPIpublic interface ExtensionFactory { <T> T getExtension(Class<T> type, String name);}

該接口在 Dubbo 中有三個默認實現類:

org.apache.dubbo.config.spring.extension.SpringExtensionFactoryorg.apache.dubbo.common.extension.factory.AdaptiveExtensionFactoryorg.apache.dubbo.common.extension.factory.SpiExtensionFactory

在開發中 SpringExtensionFactory 應該會用的更廣泛一些,本示例中此處暫時不展開。

1 AdaptiveExtensionFactory

AdaptiveExtensionFactory 是默認工廠:

import org.apache.dubbo.common.extension.Adaptive;import org.apache.dubbo.common.extension.ExtensionFactory;import org.apache.dubbo.common.extension.ExtensionLoader;import java.util.ArrayList;import java.util.Collections;import java.util.List;@Adaptivepublic class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; // 構造器 public AdaptiveExtensionFactory() { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader .getExtensionLoader(ExtensionFactory.class); // AdaptiveExtensionFactory 會將 SpiExtensionFactory 和 SpringExtensionFactory 放置在 factories 列表裡 List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } // 使用 AdaptiveExtensionFactory 去獲取實體類的時候, // 會調用 spi 或者 spring 的 ext 工廠去嘗試獲取實體類 @Override public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }}

2 SpiExtensionFactory

import org.apache.dubbo.common.extension.ExtensionFactory;import org.apache.dubbo.common.extension.ExtensionLoader;import org.apache.dubbo.common.extension.SPI;public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { // spi 工廠用於解析 spi 註解 if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { // 如果傳入的 type 是一個被 spi 注釋的接口,那麼會初始化一個它的 class loader ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); // 初始化對象 if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); } } return null; }}

四 getExtension

回到 demo :

SpiDemoService one = loader.getExtension("one");

1 getExtension

// org.apache.dubbo.common.extension.ExtensionLoaderpublic T getExtension(String name) { // 非空驗證,忽略 if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } // 默認機制,會去找名稱為 dubbo 的實例 // 一般用不到 if ("true".equals(name)) { return getDefaultExtension(); } // 如果之前不存在這個名稱對應的 Holder 對象,此處會創建一個空白的 Holder // 調用 get() 方法會獲得空對象 final Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 創建對象實例,並裝進 holder 中 instance = createExtension(name); holder.set(instance); } } } // 返回實例 return (T) instance;}

2 createExtension

// org.apache.dubbo.common.extension.ExtensionLoaderprivate T createExtension(String name) { // 如果 class 不存在就報錯 Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { // 嘗試獲取 class 對應的實例,如果不存在就創建 T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 進行依賴注入 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); }}

五 一點嘮叨

1 總結

spi 的代碼有點繞,做一下總結。

ExtensionLoader

ExtensionLoader 是整個 spi 系統的門面,也是 spi 的實例結合類。

內置對象

  • EXTENSION_LOADERS

記錄了 class 和 loader 的對應關係

  • EXTENSION_INSTANCES

記錄了 class 和實例對象的對應關係。可以認為是一個靜態的全局 bean 容器。

主要方法

  • getExtensionLoader(Class)

嘗試創建這個 class 對應的 loader 對象;

但是在創建一般 spi 接口的 loader 對象之前,還會先創建 ExtensionFactory 的 loader 對象。

  • getExtension(String)

按照需求實例化對象,放置到 EXTENSION_INSTANCES 對象中,然後做依賴注入並返回給使用者。

ExtensionFactory

用來做依賴注入的工廠。

ExtensionFactory 也是被 ExtensionLoader 管理的一類特殊的 spi 類。

2 未完成

dubbo 的 spi 還有很重要的一部分即為 @Adaptive 註解的使用,這部分涉及動態代理,較為複雜,有空開新篇講。

本文僅為個人的學習筆記,可能存在錯誤或者表述不清的地方,有緣補充

來源:https://www.tuicool.com/articles/fMBzMvU

相關焦點

  • 教你分析Dubbo源碼中的SPI和自適應擴展機制
    Dubbo SPI 介紹Dubbo 並未使用 Java 原生的 SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重要的模塊。基於 SPI,我們可以很容易的對 Dubbo 進行拓展。如果大家想要學習 Dubbo 的源碼,SPI 機制務必弄懂。
  • Dubbo SPI中AOP實現原理
    官網:http://dubbo.apache.org/zh-cn/docs/2.7/source_code_guide/dubbo-spi/從官網說明中我們可以看到,dubbo spi是java spi的一種增強實現,也就是這種spi機制確保了dubbo高擴展的能力。
  • JDK源碼解析之Java SPI機制
    一旦代碼裡涉及具體的實現類,就違反了開閉原則,Java SPI就是為某個接口尋找服務實現的機制,Java Spi的核心思想就是解耦。整體機製圖如下:Java SPI 實際上是「基於接口的編程+策略模式+配置文件」組合實現的動態加載機制。總結起來就是:調用者根據實際使用需要,啟用、擴展、或者替換框架的實現策略2.
  • Apache Dubbo介紹以及擴展機制SPI
    這個問題恐怕我不會給太多答案給你,因為 Dubbo 的官網其實描述非常清楚明了,而且具備了非常全面的架構解析以及源碼解析。所以,學習 Dubbo 非常有必要去 官網傳送門。它主要從幾方面增強:按需加載,非原生的一次性加載對 SPI 加載有著更好的故障追溯更多的擴展點,例如 IOC 以及 AOP 的支持這些東西都是非常優秀的一些設計理念,也是我們在源碼解析的時候可以著重學習的地方!Let’s Go!
  • Dubbo—SPI及自適應擴展原理
    本文章將基於Dubbo2.5.3版本的源碼做分析。而Dubbo中最核心的一點就是SPI和自適應擴展,Dubbo的高擴展性以及其它功能都是在這個基礎上實現的,理解掌握其原理才能看懂後面的一系列功能的實現原理,對我們平時實現高擴展性也非常有幫助。(PS:Dubbo源碼不像Zookeeper那樣有明確的入口,可以根據官網的源碼分析指導找到。)
  • 用最清晰明了的方式講述Dubbo的SPI機制
    void start() { System.out.println("你的Thinkpad電腦正在打開......"); }}3.然後再在項目reources目錄下新建一個META-INF/services文件夾,然後再新建一個以Computer接口的全限定名命名的文件,文件內容如下所示;com.demo.spi.Maccom.demo.spi.Thinkpad
  • Dubbo自適應擴展機制
    通過SPI機制,Dubbo實現了面向插件編程,只定義了模塊的接口,實現由各插件來完成。1.從上面的使用中可以看到Dubbo對SPI擴展的主要實現在ExtensionLoader類中,關於這個類源碼的講解可以看官方文檔,講解的很詳細,這邊主要說下大概過程:根據傳入的類型從classpath中查找META-INF/dubbo/internal和META-INF/dubbo路徑下所有對應的擴展點配置文件讀取擴展點配置文件中所有的鍵值對
  • DUBBO系列(1)什麼是SPI機制
    ,並由服務加載器讀取配置文件加載實現類,這樣可以在運行時動態為接口替換實現類,我們通過 SPI 機制可以為程序提供拓展功能。本文我們介紹JDK SPI使用方法並通過分析源碼深入理解。下一篇文章介紹Dubbo自己實現的SPI機制。
  • 阿里P7終於講完了JDK+Spring+mybatis+Dubbo+SpringMvc+Netty源碼
    spring源碼解析本文從核心實現和企業應用兩個方面,由淺入深、由易到難地對Spring源碼展開了系統的講解,包括Spring的設計理念和整體架構、容器的基本實現、默認標籤的解析、自定義標籤的解析、bean的加載、容器的功能擴展、AOP、資料庫連接JDBC、整合MyBatis、事務、SpringMVC、 遠程服務
  • 跟我一起學Dubbo,SPI實戰與debug源碼,一文全搞定
    由於本篇文章主要介紹SPI機制,故服務接口只需要定義UserService接口即可。dubbo為命名,可以理解為約束規範開發吧。SPI實戰用例已經準備完成,下面可以開始著手跟進dubbo源碼中。} } catch (Throwable t) { logger.error(&34; + type + &34; + resourceURL + &34; + resourceURL, t); } }解析
  • Dubbo如何通過SPI提高框架的可擴展性?
    ,國人寫的框架和國外的果然是兩種不同的風格,Dubbo的源碼還是比較清晰容易懂的。Spring框架一個Bean的初始化過程就能繞死在源碼中.傳輸協議可以用dubbo,rmi,hessian等。網絡通信可以用mina,netty。序列化可以用fastjson,hessian2,java原生的方式等SPI 全稱為 Service Provider Interface,是一種服務發現機制。SPI 的本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。
  • 「DUBBO系列」服務超時機制源碼分析
    1 文章概述DUBBO有很多地方可以配置超時時間,可以配置在消費者,可以配置在生產者,可以配置為方法級別,可以配置為接口級別,還可以配置為全局級別,DUBBO官方文檔介紹這些配置優先級如下:第一優先級:方法級 > 接口級 > 全局級第二優先級:消費者 > 生產者本文從源碼層面對超時機制進行分析
  • 理解 ServiceLoader類與SPI機制
    一、使用案例通常情況下,使用ServiceLoader來實現SPI機制。 SPI 全稱為 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。SPI是一種動態替換發現的機制, 比如有個接口,想運行時動態的給它添加實現,你只需要添加一個實現。
  • 俯瞰Dubbo全局,閱讀源碼前必須掌握這些!!
    為使更多童鞋受益,現給出開源框架地址:https://github.com/sunshinelyz/mykit-delay既然是要寫深度解析Dubbo源碼的系列專題,我們首先要做的就是搭建一套Dubbo的源碼環境,正所謂「工欲善其事,必先利其器」。
  • 大廠面試系列(五):Dubbo和Spring Cloud
    dubbo的spi思想dubbo進行的服務治理、服務降級、失敗以及重試。服務端怎麼知道客戶端要調用的算法的?闡述下dubbo的架構dubbo支持的註冊中心有哪些,分別的優缺點dubbo執行流程?dubbo和springclond的架構區別和優劣?說一下dubbo的實現過程?註冊中心掛了可以繼續通信嗎?
  • dubbo學習之源碼創建屬於自己的dubbo-demo
    推薦學習 環境搭建藉助官網的快速啟動手冊,以及源碼包路徑;分別在dubbo-demo-interfacemodule--暴露服務接口,ref:指接口真正的實現類bean,實現類後面會分享--> <dubbo:service interface=&34; ref=&34;/></beans>注意:此處與官網提供的xml配置存在區別,溪源這裡引入包路徑掃描機制,否則接口實現類無法使用註解@Service等注入spring
  • Dubbo源碼解析之服務端Provider
    分析原理這裡只是分析下大概原理,給各位同學先帶來帶你感受,實際步驟後面分析源碼時候再細說在進行分析之前我們思考一下,當我們不使用RPC框架和SpringCloud的時候,如果我們要調用其他第三方的服務時候,我們會怎麼處理呢?通過下面這種方式每次調用時候構建一個HTTP的請求。
  • 「DUBBO系列」服務降級源碼分析
    我們通過分析源碼講解服務降級策略,首先看一個消費者代碼實例。:application name=&34; /> <dubbo:registry address=&34; /> <dubbo:reference id=&34; interface=&34; /></beans>MockClusterInvoker正是服務降級核心// 通過動態代理機制生成代理對象
  • 「java」java的SPI機制簡介
    基本介紹服務提供發現接口(Service Provider Interface,SPI)是Java提供的一種服務發現機制。SPI的主要作用就是為某個接口尋找服務實現。資料庫驅動:不同資料庫廠商實現統一標準的驅動日誌:基於SLF4J標準的日誌實現Dubbo:Dubbo中大量使用了類似SPI思想的方式實現框架擴展使用Java實現SPI例子1、先定義一個計劃要被擴展的接口package spi
  • Dubbo源碼學習——從源碼看看dubbo對netty的使用
    前言前段時間,從頭開始將netty源碼了解了個大概,但都是原理上理解。剛好博主對dubbo框架了解過一些,這次就以dubbo框架為例,詳細看看dubbo這種出色的開源框架是如何使用netty的,又是如何與框架本身邏輯進行融合的。