一、概述
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功能等知識點,我們都暫且繞過了,后面我們會在下一篇文章中逐一介紹。