Dubbo源碼分析之 SPI(一)


一、概述

    dubbo SPI 在dubbo的作用是基礎性的,要想分析研究dubbo的實現原理、dubbo源碼,都繞不過 dubbo SPI,掌握dubbo SPI 是征服dubbo的必經之路。

    本篇文章會詳細介紹dubbo SPI相關的內容,通過源碼分析,目標是讓讀者能通過本篇文章,徹底征服dubbo SPI。

    文章的組織方式是先介紹SPI 的概念,通過Java SPI 讓大家了解SPI 是什么,怎么用,有一個初步的概念,dubbo的SPI是擴展了Java SPI的內容,自己實現了一個SPI。

二、SPI概念介紹

    SPI全稱 Service Provider Interface,是一種服務發現機制。我們編程實現一個功能時,經常先抽象一個interface,內部再定一些方法。具體的實現交給 implments了此接口的類,實現了功能和實現類的分離。

    我們設想一下,如果一個描述汽車功能的interface  Car,存在多個實現類BMW、Benz、Volvo,某個場景調用Car的行駛方法,程序就需要確認到底是需要BMW、Benz、Volvo中的那個類對象。如果硬編碼到程序中,固然可以,但是出現了接口和實現類的耦合,缺點也顯而易見。

    有辦法做到在調用代碼中不涉及BMW、Benz、Volvo字符,也隨意的指定實現類么?當然,SPI就是解決這個問題。

    SPI的實現方式是,在指定的路徑下增加一個文本文件,文件的名稱是interface的全限定名(包名+接口名),文件內容每行是一個此接口的實現類的全限定名,多個實現類就會有多行。接口進行調用時,根據接口全限定名,先讀取文本文件,解析出具體的實現類,通過反射進行實例化,再調用之。如果增加新的實現類,不需要修改調用代碼,只需要在文本文件中增加一行實現類的全限定名即可,刪除實現類同理。

三、Java SPI 介紹

    我們先看看Java的SPI怎么實現的,通過一個demo,進行了解。

    先定一個小車的接口,有一個方法 goBeijing():

1 package cn.hui.spi
2 //Car 接口
3 public interface Car {
4 
5     // 開車去北京
6     void goBeijing();
7 
8 }

    我們建三個實現類:

    

 1 package cn.hui.spi.impl;
 2 public class Bmw implements Car{
 3     @Override
 4     public void goBeijing() {
 5         // TODO Auto-generated method stub
 6         System.out.println("開着寶馬去北京......");
 7     }
 8 }
 9 
10 package cn.hui.spi.impl;
11 public class Benz implements Car{
12     @Override
13     public void goBeijing() {
14         // TODO Auto-generated method stub
15         System.out.println("開着奔馳去北京........");
16     }
17 }
18 
19 package cn.hui.spi.impl;
20 public class Volvo implements Car {
21     @Override
22     public void goBeijing() {
23         // TODO Auto-generated method stub
24         System.out.println("開着沃爾沃去北京......");
25     }
26 }

    我們在 "META-INF/services" 文件夾下新建一個文件,名稱為“cn.hui.spi.Car",文件內容:

    

1 cn.hui.spi.impl.Bmw
2 cn.hui.spi.impl.Benz
3 cn.hui.spi.impl.Volvo

     方法調用的代碼如下:

    

 1 import java.util.Iterator;
 2 import java.util.ServiceLoader;
 3 public class App {
 4     
 5     public static void main(String[] args) {
 6         ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);
 7         Iterator<Car> iterator = serviceLoader.iterator();
 8         while(iterator.hasNext()) {
 9             Car car = iterator.next();
10             car.goBeijing();
11         }
12     }
13 }

    打印結果:

1 開着寶馬去北京......
2 開着奔馳去北京........
3 開着沃爾沃去北京......

    這個就是Java SPI簡單實現方式。

三、Dubbo SPI介紹

    dubbo 在Java SPI的基礎上進行了功能擴展,我們再看上面的Java SPI示例,可以發現很明顯的問題,對文本文件的加載后,實例化對象是一次性全部進行實例化,得到一個實現類的對象集合,調用的時候循環執行。不能唯一指定一個實現類進行唯一調用。dubbo通過在文本文件中指定每個實現類的key,唯一標識出每個實現類,調用的時候可以指定唯一的一個實現類。同樣實例化也不需要一次性全部實例化了,只需要實例化需要調用的類即可。

     同時dubbo還實現了IOC和AOP的功能,接口的實現類之間可以進行相互的注入,了解Spring的同學,應該很清楚IOC和AOP的邏輯,下面我們現在熟悉下dubbo SPI的相關概念,之后在通過一個簡單的樣例,了解dubbo SPI 的使用。

四、Dubbo SPI關鍵點

    dubbo SPI的功能主要是通過ExtensionLoader類實現,dubbo啟動時,默認掃描三個目錄:META-INF/services/、META-INF/dubbo/、META-INF/internal/,在這三個目錄下的文本文件都會加載解析,文本文件的內容:key=實現類的全限定名。

    dubbo把接口定義為 擴展點,實現類定義為 擴展點實現,所有的擴展點(接口)需要進行@SPI注解,更多的功能和注解我們逐步介紹。

    dubbo在啟動的時候掃描文本文件,對文件內容進行解析,但是不會全部進行實例化,只有在調用到具體的擴展點實現時,才會進行特定擴展點的實例化。

    同時dubbo SPI提供自適應擴展、默認擴展、自動激活擴展等功能,我們后面介紹。

五、Dubbo SPI示例

    我們把上面Car接口的例子,改造成基於dubbo SPI的實現。進行配置的文本文件內容。

    在擴展點實現類前都加上key,改為:

1 bmw=cn.hui.spi.impl.Bmw
2 benz=cn.hui.spi.impl.Benz
3 volvo=cn.hui.spi.impl.Volvo

 Car接口改造為:

1 @SPI
2 public interface Car {
3     // 開車去北京
4     void goBeijing();
5 }

擴展點,暫時不做修改,我們看看調用方法:

 1 public class App {
 2     public static void main(String[] args) {
 3         Car car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("bmw");
 4         car.goBeijing();
 5         car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("benz");
 6         car.goBeijing();
 7         car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("volvo");
 8         car.goBeijing();
 9     }
10 }

此時,控制台會出現:

1 開着寶馬去北京......
2 開着奔馳去北京........
3 開着沃爾沃去北京......

這個就是簡單dubbo使用,復雜的功能我們放到源碼分析的時候進行。

六、Dubbo SPI 源碼分析

    dubbo SPI的功能主要幾種在ExtensionLoader類中實現,分析源碼也就主要分析此類,我們通過ExtensionLoader對外提供的方法作為入口進行源碼分析。

    需要注意:一個type接口對應一個ExtensionLoader 實例。

    上面的示例中,我們通過 getExtensionLoader(..)方法,獲得ExtensionLoader實例,ExtensionLoader類的構造方法是私有的,只能通過此方法獲取實例。

    我們先看看此方法:

 1 @SuppressWarnings("unchecked")
 2 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
 3     if (type == null) {
 4         throw new IllegalArgumentException("Extension type == null");
 5     }
 6     // 必須是接口
 7     if (!type.isInterface()) {
 8         throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
 9     }
10     // 必須被@SPI注解
11     if (!withExtensionAnnotation(type)) {
12         throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
13     }
14     // extension_loaders 為成員變量,是 type---> ExtensionLoader 實例的緩存
15     ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
16     if (loader == null) {
17         // putIfAbsent put不覆蓋
18         EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
19         loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
20     }
21     return loader;
22 }

我們看到該方法主要是先對type進行校驗,再根據type為key,從緩存EXTENSION_LOADERS中獲取ExtensionLoader實例,如果緩存沒有,則新建一個ExtensionLoader實例,並放入緩存。
注意,我們說過一個type對應一個ExtensionLoader實例,為什么還需要緩存呢,我們再看看 EXTENSION_LOADERS的定義:

// 擴展點接口和對應ExtensionLoader實例的緩存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

沒錯,EXTENSION_LOADERS 是一個static、final修飾的類靜態變量。

我們接着看上面,看一下ExtensionLoader的構造方法:

 1 private ExtensionLoader(Class<?> type) {
 2     this.type = type;
 3     // type 為ExtensionFactory時,objectFactory為空
 4     if (type == ExtensionFactory.class) {
 5         objectFactory = null;
 6     } else {
 7         // type為普通接口時,objectFactory為AdaptiveExtensionFactory,負責dubbo spi 的IOC 功能
 8         objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
 9     }
10 //        objectFactory = (type == ExtensionFactory.class ? null
11 //                : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
12 }

構造方法私有,不能直接在外部new出實例。

方法內部,參數type賦值給成員變量type,還會進行ExtensionFactory類判斷,ExtensionFactory是實現IOC功能的,我們此處暫時繞過,后面進行介紹。
我們總結一下getExtensionLoader(..)方法,繞開ExtensionFactory,就是new 了一個ExtensionLoader對象實例,為成員變量type賦值為擴展點type,對象實例放入EXTENSION_LOADERS 緩存中。
現在我們有了ExtensionLoader實例對象,我們再看看獲取type實例的方法:getExtension(..):

 1 @SuppressWarnings("unchecked")
 2 public T getExtension(String name) {
 3     if (name == null || name.length() == 0)
 4         throw new IllegalArgumentException("Extension name == null");
 5     if ("true".equals(name)) {
 6         // 獲取默認的擴展實現類
 7         return getDefaultExtension();
 8     }
 9     // Holder僅用於持有目標對象,沒有其他邏輯
10     Holder<Object> holder = cachedInstances.get(name);
11     if (holder == null) {
12         cachedInstances.putIfAbsent(name, new Holder<Object>());
13         holder = cachedInstances.get(name);
14     }
15     Object instance = holder.get();
16     if (instance == null) {
17         synchronized (holder) {
18             instance = holder.get();
19             if (instance == null) {
20                 // 創建擴展實例,並設置到holder中
21                 instance = createExtension(name);
22                 holder.set(instance);
23             }
24         }
25     }
26     return (T) instance;
27 }

    方法的入參name為提供配置的文本文件中的key,還記得我們的文本文件中的內容吧,其中一行:bmw=cn.hui.spi.impl.Bmw,此處的name 就是 bmw。 如果name為true,返回getDefaultExtension(),這個方法我們暫時繞過。

 我們看到13行,根據name從cachedInstances中獲取Holder對象,很明顯 cachedInstances就是一個存放對象的緩存,緩存中沒有new一個新的實例,至於Holder,我們看下這個類:

 1 // 持有目標對象
 2 public class Holder<T> {
 3 
 4     private volatile T value;
 5 
 6     public void set(T value) {
 7         this.value = value;
 8     }
 9 
10     public T get() {
11         return value;
12     }
13 
14 }

只是存放對象,沒有任何邏輯。

我們接着看到ExtensionLoader類的代碼,在拿到holder實例后,我們要從hodler中獲取擴展點的實例:

 1 Object instance = holder.get();
 2 if (instance == null) {
 3     synchronized (holder) {
 4         instance = holder.get();
 5         if (instance == null) {
 6             // 創建擴展實例,並設置到holder中
 7             instance = createExtension(name);
 8             holder.set(instance);
 9         }
10     }
11 }

如果holder中沒有擴展點的實例,通過雙檢鎖,通過調用 createExtension方法 返回擴展點實例。並放入holder對象中。

到此,我們發現new擴展點實例進到 createExtension方法中。

我們接着分析此方法:

 1 // 創建擴展對象實例
 2 @SuppressWarnings("unchecked")
 3 private T createExtension(String name) {
 4     // 從配置文件中加載所有的擴展類,形成配置項名稱到配置類Clazz的映射關系
 5     Class<?> clazz = getExtensionClasses().get(name);
 6     if (clazz == null) {
 7         throw findException(name);
 8     }
 9     try {
10         T instance = (T) EXTENSION_INSTANCES.get(clazz);
11         if (instance == null) {
12             // 通過反射創建實例
13             EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
14             instance = (T) EXTENSION_INSTANCES.get(clazz);
15         }
16         // 向實例中注入依賴,IOC實現
17         injectExtension(instance);
18         // 包裝處理
19         // cachedWrapperClasses 加載@SPI配置時賦值,此處進行實例化
20         Set<Class<?>> wrapperClasses = cachedWrapperClasses;
21         if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
22             // 循環創建wrapper實例
23             for (Class<?> wrapperClass : wrapperClasses) {
24                 // 將當前instance作為參數創建wrapper實例,然后向wrapper實例中注入屬性值,
25                 // 並將wrapper實例賦值給instance
26                 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
27             }
28         }
29         return instance;
30     } catch (Throwable t) {
31         throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ")  could not be instantiated: " + t.getMessage(), t);
32     }
33 }

    我們看到方法開始就通過 Class<?> clazz = getExtensionClasses().get(name); 獲取Class對象,可以直觀的看出通過name獲得的這個clazz是在配置的文本文件中name對應的擴展點實現類的Class對象,關於getExtensionClasses方法,我們稍后分析,接着往下看:

1 T instance = (T) EXTENSION_INSTANCES.get(clazz);
2 if (instance == null) {
3     // 通過反射創建實例
4     EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
5     instance = (T) EXTENSION_INSTANCES.get(clazz);
6 }

通過clazz對象,從EXTENSION_INSTANCES獲取緩存的實例,如果獲取不到,通過反射clazz.newInstance() new一個新的實例對象,並放入EXTENSION_INSTANCES中。

我們可以看到,擴展點的實現類 必須要有一個默認無參的構造函數。

接着往下看:

1  // 向實例中注入依賴,IOC實現
2 injectExtension(instance);

此方法是實現IOC功能,我們暫且繞過。

接下來,我們看到:

 1  // 包裝處理
 2  // cachedWrapperClasses 加載@SPI配置時賦值,此處進行實例化
 3  Set<Class<?>> wrapperClasses = cachedWrapperClasses;
 4  if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
 5      // 循環創建wrapper實例
 6      for (Class<?> wrapperClass : wrapperClasses) {
 7          // 將當前instance作為參數創建wrapper實例,然后向wrapper實例中注入屬性值,
 8          // 並將wrapper實例賦值給instance
 9          instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
10      }
11  }

此處是處理包裝類的,我們也暫且繞過。下面就是直接返回擴展點的instance實例了 

1 return instance;

 現在我們還有一個方法沒有分析,就是加載擴展點實現類的Class對象的方法getExtensionClasses()。我們現在來看這個方法:

 1 private Map<String, Class<?>> getExtensionClasses() {
 2     Map<String, Class<?>> classes = cachedClasses.get();
 3     if (classes == null) {
 4         synchronized (cachedClasses) {
 5             classes = cachedClasses.get();
 6             if (classes == null) {
 7                 classes = loadExtensionClasses();
 8                 cachedClasses.set(classes);
 9             }
10         }
11     }
12     return classes;
13 }

我們看到,這個方法返回的是一個Map對象,可以確認的是,這個Map存放的是擴展點的所有實現類的Class,Map的key就是配置的文本文件的name。如果緩存cachedClasses 中存在,即返回,如果沒有,通過loadExtensionClasses()加載,並設置到cachedClasses中。

我們接着看loadExtensionClasses方法:

 1 private Map<String, Class<?>> loadExtensionClasses() {
 2     // 獲取注解 SPI的接口
 3     // type為傳入的擴展接口,必須有@SPI注解
 4     final SPI defaultAnnotation = type.getAnnotation(SPI.class);
 5     // 獲取默認擴展實現value,如果存在,賦值給cachedDefaultName
 6     if (defaultAnnotation != null) {
 7         String value = defaultAnnotation.value();
 8         if ((value = value.trim()).length() > 0) {
 9             // @SPI value 只能是一個,不能為逗號分割的多個
10             // @SPI value為默認的擴展實現
11             String[] names = NAME_SEPARATOR.split(value);
12             if (names.length > 1) {
13                 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
14             }
15             if (names.length == 1)
16                 cachedDefaultName = names[0];
17         }
18     }
19     // 加載三個目錄配置的擴展類
20     Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
21     // META-INF/dubbo/internal
22     loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
23     // META-INF/dubbo
24     loadDirectory(extensionClasses, DUBBO_DIRECTORY);
25     // META-INF/services/
26     loadDirectory(extensionClasses, SERVICES_DIRECTORY);
27     return extensionClasses;
28 }

我們看到方法內部的邏輯,首先判斷擴展點接口type是否用@SPI注解,在前面的方法中,已經判斷,如果沒有@SPI注解,拋出異常,此處type必定存在@SPI注解。

根據注解獲取到defaultAnnotation 對象,目的是拿到@SPI中的value,且value值不能用逗號分隔,只能有一個,賦值給cachedDefaultName。

接着定一個了Map對象extensionClasses,作為方法的返回值,我們知道,這個方法的返回值最后設置到了緩存cachedClasses中。我們看看這個extensionClasses是怎么賦值的。這個對象主要是”經歷“了三個方法(其實是同一個方法loadDirectory,只是入參不同)。這三個方法的入參是extensionClasses 和一個目錄參數,就是前面我們介紹的dubbo默認三個目錄:

1 META-INF/services/
2 META-INF/dubbo/
3 META-INF/dubbo/internal/

我們再具體看方法loadDirectory的內容:

 1 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
 2     // 擴展配置文件完整文件路徑+文件名
 3     String fileName = dir + type.getName();
 4     try {
 5         Enumeration<java.net.URL> urls;
 6         // 獲取類加載器
 7         ClassLoader classLoader = findClassLoader();
 8         if (classLoader != null) {
 9             urls = classLoader.getResources(fileName);
10         } else {
11             urls = ClassLoader.getSystemResources(fileName);
12         }
13         if (urls != null) {
14             while (urls.hasMoreElements()) {
15                 java.net.URL resourceURL = urls.nextElement();
16                 // 加載
17                 loadResource(extensionClasses, classLoader, resourceURL);
18             }
19         }
20     } catch (Throwable t) {
21         logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t);
22     }
23 }

首先組合目錄參數和type名稱,作為文件的真實路徑名,通過加載器進行加載,之后調用loadResource方法,同時extensionClasses 傳入該方法。

 1 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
 2     try {
 3         BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
 4         try {
 5             String line;
 6             while ((line = reader.readLine()) != null) {
 7                 // 字符#是注釋開始標志,只取#前面的字符
 8                 final int ci = line.indexOf('#');
 9                 if (ci >= 0)
10                     line = line.substring(0, ci);
11                 line = line.trim();
12                 if (line.length() > 0) {
13                     try {
14                         String name = null;
15                         int i = line.indexOf('=');
16                         if (i > 0) {
17                             // 解析出 name 和 實現類
18                             name = line.substring(0, i).trim();
19                             line = line.substring(i + 1).trim();
20                         }
21                         if (line.length() > 0) {
22                             loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
23                         }
24                     } catch (Throwable t) {
25                         IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
26                         exceptions.put(line, e);
27                     }
28                 }
29             }
30         } finally {
31             reader.close();
32         }
33     } catch (Throwable t) {
34         logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
35     }
36 }

 這個方法就簡單多了,解析文件流,拿到配置文本文件中的key、value,同時通過Class.forName(..)加載解析出來的擴展點實現類,傳入方法loadClass,注意這個方法傳入的參數還有存放key、Class的Map對象extensionClasses,以及配置文本文件中的key,我們再看這個方法:

 1 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
 2     // type是否為clazz的超類,clazz是否實現了type接口
 3     // 此處clazz 是擴展實現類的Class
 4     if (!type.isAssignableFrom(clazz)) {
 5         throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
 6     }
 7     // clazz是否注解了 Adaptive 自適應擴展
 8     // 不允許多個類注解Adaptive
 9     // 注解adaptive的實現類,賦值給cachedAdaptiveClass
10     if (clazz.isAnnotationPresent(Adaptive.class)) {
11         if (cachedAdaptiveClass == null) {
12             cachedAdaptiveClass = clazz;
13             // 不允許多個實現類都注解@Adaptive
14         } else if (!cachedAdaptiveClass.equals(clazz)) {
15             throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
16         }
17         // 是否為包裝類,判斷擴展類是否提供了參數是擴展點的構造函數
18     } else if (isWrapperClass(clazz)) {
19         Set<Class<?>> wrappers = cachedWrapperClasses;
20         if (wrappers == null) {
21             cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
22             wrappers = cachedWrapperClasses;
23         }
24         wrappers.add(clazz);
25         // 普通擴展類
26     } else {
27         // 檢測 clazz 是否有默認的構造方法,如果沒有,則拋出異常
28         clazz.getConstructor();
29         // 此處name為 SPI配置中的key
30         // @SPI配置中key可以為空,此時key為擴展類的類名(getSimpleName())小寫
31         if (name == null || name.length() == 0) {
32             // 兼容舊版本
33             name = findAnnotationName(clazz);
34             if (name.length() == 0) {
35                 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
36             }
37         }
38         // 逗號分割
39         String[] names = NAME_SEPARATOR.split(name);
40         if (names != null && names.length > 0) {
41             // 獲取Activate注解
42             Activate activate = clazz.getAnnotation(Activate.class);
43             if (activate != null) {
44                 cachedActivates.put(names[0], activate);
45             }
46             for (String n : names) {
47                 if (!cachedNames.containsKey(clazz)) {
48                     cachedNames.put(clazz, n);
49                 }
50                 // name不能重復
51                 Class<?> c = extensionClasses.get(n);
52                 if (c == null) {
53                     extensionClasses.put(n, clazz);
54                 } else if (c != clazz) {
55                     throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
56                 }
57             }
58         }
59     }
60 }

 方法參數clazz就是傳過來的擴展點實現類的Class對象,首先判斷是否實現了擴展點type接口。接着判斷是否注解了@Adaptive以及是否為包裝類isWrapperClass(clazz),這兩個分支邏輯 我們暫且繞過,接下來會進行構造器檢查,判斷是否存在無參構造器,如果name為空,為了兼容老版本 會進行一次name賦值。

此處會再進行一次name的分隔,前門已經知道,name中不會存在逗號的,但經過上面兼容老版本的重新賦值,會再進行一次判斷。@Activate注解的判斷,我們也暫且繞過。

循環解析過的name字符串,把加載的擴展點實現Class對象和name存放到入參extensionClasses中。

至此,解析、加載配置文本文件的邏輯已經結束。最后的結果主要是有:把加載到的擴展點Class和key存入到緩存對象extensionClasses中,同時設置cachedDefaultName為擴展點注解@SPI中的value。

我們重新回到方法createExtension中,現在我們已經拿到了特定name對應的擴展點實現類的Class對象,如果對象為空,拋出異常。

接着,我們從緩存對象EXTENSION_INSTANCES中,通過Class對象獲取實例,如果實例為空,通過clazz.newInstance()創建,並放入EXTENSION_INSTANCES中。

createExtension方法的后面的邏輯:

 1 // 向實例中注入依賴,IOC實現
 2 injectExtension(instance);
 3 // 包裝處理
 4 // cachedWrapperClasses 加載@SPI配置時賦值,此處進行實例化
 5 Set<Class<?>> wrapperClasses = cachedWrapperClasses;
 6 if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
 7     // 循環創建wrapper實例
 8     for (Class<?> wrapperClass : wrapperClasses) {
 9         // 將當前instance作為參數創建wrapper實例,然后向wrapper實例中注入屬性值,
10         // 並將wrapper實例賦值給instance
11         instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
12     }
13 }

 是拿到擴展點的實例之后,后期的處理,包括對IOC的實現,包裝類的處理等功能邏輯,這些知識點,我們稍后進行分析。

七、總結

    總結一下,本篇文章,我們分析了dubbo SPI的主流程,從入門介紹、示例描述到源碼分析,主流程基本介紹完了,中間涉及到的@Adaptive、@Activate注解,以及包裝類、擴展點實現類的IOC功能等知識點,我們都暫且繞過了,后面我們會在下一篇文章中逐一介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM