總結/朱季謙
本文主要記錄我對Dubbo SPI實現原理的理解,至於什麼是SPI,我這裏就不像其他博文一樣詳細地從概念再到Java SPI細細分析了,直接開門見山來分享我對Dubbo SPI的見解。
Dubbo SPI的機制比較類似Spring IOC的getBean()加載,當傳入一個存在的beanName,就可以返回該beanName對應的對象。同理,在Dubbo SPI中,我們同樣傳入一個存在的name,Dubbo框架會自動返回該key對應的對象。不難猜測,Dubbo SPI與Spring IOC在底層應該都有一個大致相似的邏輯,簡單的說,就是兩者都可通過beanName或key值,到框架裏的某個map緩存當中,找到對應的class類名,然後對該class類名進行反射生成對象,初始化完成該對象,最後返回一個完整的對象。然而,在這個過程當中,Spirng相對來說會更復雜,它裏面還有一堆後置處理器......
簡單舉一個例子,大概解釋一下Dubbo SPI實現原理,然後再進一步分析源碼。
首先,我在org.apache.dubbo.test目錄下,定義一個@SPI註解到接口:
package org.apache.dubbo.test;
import org.apache.dubbo.common.extension.SPI;
@SPI("dog")
public interface Animal {
void haveBehavior();
}
然後,在同一個目錄下,創建兩個實現該接口的類,分別爲Dog,Cat。
Dog——
package org.apache.dubbo.test;
public class Dog implements Animal {
@Override
public void haveBehavior() {
System.out.println("狗會叫");
}
}
Cat——
package org.apache.dubbo.test;
public class Cat implements Animal {
@Override
public void haveBehavior() {
System.out.println("貓會抓老鼠");
}
}
注意看,Animal接口的類名爲org.apache.dubbo.test.Animal,接下來,我們在resource目錄的/META_INF/dubbo需新建一個對應到該接口名的File文件,文件名與Animal接口的類名一致:org.apache.dubbo.test.Animal。之所以兩者名字要一致,是因爲這樣只需拿到Animal接口的類名,到resource目錄的/META_INF/dubbo,就可以通過該類名,定位到與Animal接口相對應的文件。
在Dubbo中,文件名org.apache.dubbo.test.Animal的文件裏,其實存了類似Spring bean那種的數據,即id對應bean class的形式——
cat=org.apache.dubbo.test.Cat
dog=org.apache.dubbo.test.Dog
這兩行數據,分別是Animal接口的實現類Cat和Dog的class全限名。
整個的目錄結構是這樣的——
最後寫一個測試類DubboSPITestTest演示一下效果——
package org.apache.dubbo.test;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;
class DubboSPITestTest {
@Test
public void test(){
Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");
dog.haveBehavior();
Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
cat.haveBehavior();
}
}
執行結果如下——
先簡單捋一下這個思路是怎麼實現的,ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")這行代碼內部,會根據Animal接口完整名org.apache.dubbo.test.Animal找到某個指定目錄下的同名File文件org.apache.dubbo.test.Animal,然後按行循環解析文件裏的內容,以key-value形式加載到某個map緩存裏。
類似這樣操作(當然,源碼裏會更復雜些)——
Map<String,String> map = new HashMap<>();
map.put("cat","org.apache.dubbo.test.Cat");
map.put("dog","org.apache.dubbo.test.Dog");
當然,真實源碼裏,value存的是已根據類名得到的Class,但其實無論是類名還是Class,最後都是可以反射生成對象的,
這時候,就可以根據運行代碼去動態獲取到該接口對應的實現類了。例如,需要使用的是org.apache.dubbo.test.Cat這個實現類,那麼在調用getExtension("cat")方法中,我們傳入的參數是"cat",就會從剛剛解析文件緩存的map中,根據map.get("cat")拿到對應org.apache.dubbo.test.Cat。既然能拿到類名了,不就可以通過反射的方式生成該類的對象了嗎?當然,生成的對象裏可能還有屬性需要做注入操作,這就是Dubbo IOC的功能,這塊會在源碼分析裏進一步說明。當對象完成初始化後,就會返回生成的對象指向其接口引用Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")。
整個過程,就是可根據代碼去動態獲取到某個接口的實現類,方便靈活調用的同時,實現了接口和實現類的解耦。
Dubbo SPI是在Java SPI基礎上做了拓展,Java SPI中與接口同名的文件裏,並不是key- value的形式,純粹是按行來直接存放各實現類的類名,例如這樣子——
org.apache.dubbo.test.Cat
org.apache.dubbo.test.Dog
這就意味着,Java SPI在實現過程中,通過接口名定位讀取到resource中接口同名文件時,是無法做到去選擇性地根據某個key值來選擇某個接口的實現類,它只能全部讀取,再全部循環獲取到對應接口實現類調用相應方法。這就意味着,可能存在一些並非需要調用的實現類,也會被加載並生成對象一同返回來,無法做到按需獲取。
因此,Dubbo在原有基礎上,則補充了Java SPI無法按需通過某個key值去調用指定的接口實現類,例如上面提到的,Dubbo SPI可通過cat這個key,去按需返回對應的org.apache.dubbo.test.Cat類的實現對象。
下面就來分析一下具體實現的原理細節,以下代碼做案例。
Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
cat.haveBehavior();
先來分析ExtensionLoader.getExtensionLoader(Animal.class)方法——
public 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!");
}
//判斷是否具有@SPI註解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
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;
}
在這個方法裏,若傳進來的Class參數爲空或者非接口或者沒有@SPI註解,都會拋出一個IllegalArgumentException異常,說明傳進來的Class必須需要滿足非空,爲接口,同時具有@SPI註解修飾,才能正常往下執行。我們在這裏傳進來的是Animal.class,它滿足了以上三個條件。
@SPI("cat")
public interface Animal {
void haveBehavior();
}
接下來,在最後這部分代碼裏,主要是創建一個ExtensionLoader對象。
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;
最後,返回的也是創建的ExtensionLoader對象,該對象包括了兩個東西,一個是type,一個是objectFactory。這兩個東西在後面源碼裏都會用到。
創建完ExtensionLoader對象後,就會開始調用getExtension方法——
Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
進入到getExtension("cat")方法當中,內部會調用另一個重載方法getExtension(name, true)。
public T getExtension(String name) {
return getExtension(name, true);
}
讓我們來看一下該方法內部實現——
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
//step 1
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
//step 2
//雙重檢查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//創建擴展實例
instance = createExtension(name, wrap);
//設置實例到holeder中
holder.set(instance);
}
}
}
return (T) instance;
}
該方法主要有兩部分。
step 1,先從緩存裏查找name爲“cat”的對象是否存在,即調用getOrCreateHolder(name),在該方法裏,會去cachedInstances緩存裏查找。cachedInstances是一個定義爲ConcurrentMap<String, Holder>的map緩存。若cachedInstances.get(name)返回null的話,說明緩存裏還沒有name對應的對象數據,那麼就會創建一個key值爲name,value值爲new Holder<>()的鍵值對緩存。
private Holder<Object> getOrCreateHolder(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}
想必你一定會有疑惑,爲什麼這裏要創建一個new Holder<>()對象呢?
進到Holder類裏,就會發現,其內部用private修飾封裝一個泛型變量value,這就意味着,外部類是無法修改該value值,能起到一個封裝保護的作用。我們正在通過name='cat'去得到一個org.apache.dubbo.test.Cat實現類對象,該對象若能正常生成,最後就會封裝到一個Holder對象裏,再將Holder對象存放到cachedInstances緩存裏。
public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
因此,就會有該從緩存裏獲取Holder的操作——
//根據name爲“cat”去緩存裏查找封裝了org.apache.dubbo.test.Cat對象的Holder對象。
final Holder<Object> holder = getOrCreateHolder(name);
//若能查找到,就從Holder對象取出內部封裝的對象
Object instance = holder.get();
若holder.get()得到的對象爲null,說明還沒有生成該“cat”對應的org.apache.dubbo.test.Cat類對象。
那麼,就會繼續往下執行——
//雙重檢查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//創建擴展實例
instance = createExtension(name, wrap);
//設置實例到holeder中
holder.set(instance);
}
}
}
這裏用到了一個雙重檢查的操作,避免在多線程情況裏出現某一個線程創建了一半,另一個線程又開始創建同樣對象,就會出現問題。
這行instance = createExtension(name, wrap)代碼,主要實現的功能,就是得到“cat”對應的org.apache.dubbo.test.Cat類對象,然後將返回的對象通過holder.set(instance)封裝在Holder對象裏。
private T createExtension(String name, boolean wrap) {
// step 1從配置文件中加載所有的擴展類,可得到"配置項名稱"到"配置類"的映射關係表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//step 2 將得到的clazz通過反射創建實例對象。
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//step 3 對實例對象的屬性做依賴注入,即Dubbo IOC邏輯。
injectExtension(instance);
if (wrap) {
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);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
//將當前instance作爲參數傳給Wrapper的構造方法,並通過反射創建Wrapper實例
//然後向Wrapper實例注入依賴,最後將Wrapper實例再次賦值給instance變量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
initExtension(instance);
//step 4 返回初始化完成的對象
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
createExtension(String name, boolean wrap)方法裏主要實現了以下
step 1 從配置文件中加載所有的擴展類,可得到"配置項名稱"到"配置類"的映射關係表。
step 2 將得到的clazz通過反射創建實例對象。
step 3 對實例對象的屬性做依賴注入,即Dubbo IOC邏輯。
step 4 返回初始化完成的對象
一、先來看第一步的代碼分析——
// step 1從配置文件中加載所有的擴展類,可得到"配置項名稱"到"配置類"的映射關係表
Class<?> clazz = getExtensionClasses().get(name);
其中getExtensionClasses()方法是獲取返回一個解析完接口對應Resource裏文件的Map<String, Class>緩存,代碼最後部分get(name)在這個案例裏,就是根據“cat”獲得“org.apache.dubbo.test.Cat”的Class。方法內部cachedClasses.get()返回的這個Map> classes正是存放了接口對應Resource文件裏key- value數據,即car=org.apache.dubbo.test.Cat和dog=org.apache.dubbo.test.Dog這類。
private Map<String, Class<?>> getExtensionClasses() {
//從緩存中獲取已加載的拓展類:car=org.apache.dubbo.test.Cat
Map<String, Class<?>> classes = cachedClasses.get();
//雙重檢查
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
當然,首次調用cachedClasses.get()返回值classes肯定爲null。這裏在classes==null時,同樣使用了一個雙重檢查的操作,最後會去調用loadExtensionClasses()方法,該方法主要做的一件事,就是去讀取到Resource中接口對應的文件進行解析,然後將解析的數據以key-value緩存到Map<String, Class<?>>裏,最後通過cachedClasses.set(classes)存入到cachedClasses裏,這裏的cachedClasses同樣是一個final定義的Holder對象,作用與前文提到的一致,都是封裝在內部以private修飾,防止被外部類破壞。
主要看下loadExtensionClasses()內部邏輯——
private Map<String, Class<?>> loadExtensionClasses() {
//step 1 對SPI註解進行解析,獲取SPI默認對value
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
/**
* step 2 strategies包含以下四種策略,代表查找四種不同目錄下的文件:
*
* DubboInternalLoadingStrategy 表示目錄"META-INF/dubbo/internal/"
* DubboExternalLoadingStrategy 表示目錄""META-INF/dubbo/external/""
* DubboLoadingStrategy 表示目錄"META-INF/dubbo/"
* ServicesLoadingStrategy 表示目錄"META-INF/services/"
*/
for (LoadingStrategy strategy : strategies) {
//加載指定文件夾下對配置文件,找到SPI默認對value的class
//apache
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
//alibaba
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
首先,執行cacheDefaultExtensionName()方法,該方法是對接口修飾的@SPI進行解析,獲取註解裏的value值。例如,在該例子裏,Animal的註解@SPI("cat"),那麼,通過cacheDefaultExtensionName()方法,即能獲取到註解@SPI裏的默認值“cat”。之所以獲取該註解的值,是用來當做默認值,即如果沒有傳入指定需要獲取的name,那麼就返回cat對應的類對象。
接着,就是遍歷四種不同目錄,查找是否有與接口Animal對應的文件。這裏的strategies是一個數組,裏面包含四種對象,每個對象代表查找某個目錄,包括"META-INF/dubbo/internal/"、"META-INF/dubbo/external/"、"META-INF/dubbo/"、"META-INF/services/",表示分別到這四種目錄去查看是否有滿足的文件。
for循環裏調用了兩次loadDirectory方法,羣分就是一個是查找apache版本的,一個是查找alibaba版本的,兩方法底層其實都是一樣,只需要關注其中一個實現即可。
/**
* step 2 strategies包含以下四種策略,代表查找四種不同目錄下的文件:
*
* DubboInternalLoadingStrategy 表示目錄"META-INF/dubbo/internal/"
* DubboExternalLoadingStrategy 表示目錄"META-INF/dubbo/external/"
* DubboLoadingStrategy 表示目錄"META-INF/dubbo/"
* ServicesLoadingStrategy 表示目錄"META-INF/services/"
*/
for (LoadingStrategy strategy : strategies) {
//加載指定文件夾下對配置文件,找到SPI默認對value的class
//apache
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
//alibaba
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
在loadDirectory方法裏,就是定位到接口對應的File文件,獲取文件的路徑,然後調用loadResource方法對文件進行解析——
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
//文件夾路徑+type 全限定名:META-INF/dubbo/internal/org.apache.dubbo.test.Animal
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
//根據文件名加載讀取所有同名文件
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//加載資源進行解析
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadResource方法主要是讀取File文件資源,然後循環遍歷文件裏的每一行記錄,跳過開頭爲#的註釋記錄,對cat=org.apache.dubbo.test.Cat形式的行記錄進行切割。通過這行代碼int i = line.indexOf('=')定位到等於號=的位置,然後以name = line.substring(0, i).trim()來截取等於號前面的字符串作爲key, 以 line = line.substring(i + 1).trim()截取等於號=後面的字符串作爲value,形成key-value鍵值對形式數據,進一步傳到 loadClass方法進行相應緩存。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
//按行循環讀取配置內容
while ((line = reader.readLine()) != null) {
//定位到 # 字符
final int ci = line.indexOf('#');
if (ci >= 0) {
//截取 # 之前的字符串,#之後的內容爲註釋,需要忽略
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
//以等於號 = 爲界,讀取key健 value值
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
//加載類,通過loadClass對類進行緩存
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
以cat=org.apache.dubbo.test.Cat數據爲例子,debug可以看到,最後解析得到的爲——
最後,到loadClass看一下是怎麼對從文件裏解析出來的key-value數據進行緩存,注意一點是,在執行該方法時,已將上文拿到的line="org.apache.dubbo.test.Cat"通過Class.forName(line, true, classLoader)生成了對應的Class。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
//檢測目標類上是否有Adaptive註解
if (clazz.isAnnotationPresent(Adaptive.class)) {
//設置cachedadaptiveClass緩存
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
//程序進入此分支,表明class是一個普通的拓展類
//檢測class是否有默認的構造方法,如果沒有,則拋出異常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
//如果name爲空,則嘗試從Extension註解中獲取name,或使用小寫的類名作爲name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
//names = ["cat"]
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
//存儲Class到名稱的映射關係
cacheName(clazz, n);
//存儲名稱到Class的映射關係
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
這裏只需要關注最後的saveInExtensionClass方法,可以看到,最後將從文件裏解析出來的“cat”-->org.apache.dubbo.test.Cat存入到Map<String, Class<?>> extensionClasses緩存當中。
這個Map<String, Class<?>> extensionClasses緩存是在loadExtensionClasses()方法裏創建的,該loadExtensionClasses方法最後會將extensionClasses進行返回。
private Map<String, Class<?>> loadExtensionClasses() {
......
//創建用來緩存存儲解析文件裏key-value數據
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
......
return extensionClasses;
}
到這一步,就完成了Animal接口對應的resource/META-INF/dubbo/org.apache.dubbo.test.Animal文件的解析,解析出來的數據存放到了extensionClasses這個Map緩存裏。
回顧先前的方法調用,可以看到,最終得到extensionClasses的map緩存,會返回到getExtensionClasses()方法,因此,在createExtension調用getExtensionClasses().get(name),就相當於是調用extensionClasses.get(name)。因爲傳到方法裏的參數name="cat",故而返回的Class即org.apache.dubbo.test.Cat。
接着往下執行,到代碼EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance())就是通過clazz.newInstance()反射創建了一個暫時還是空屬性的對象,同時緩存到EXTENSION_INSTANCES緩存裏,這是一個ConcurrentMap<Class<?>, Object>緩存,避免反覆進行反射創建對象。
實例化完成org.apache.dubbo.test.Cat對象的創建,接下來就是通過injectExtension(instance)對對象進行依賴注入了。主要功能就類似Spring IOC裏bean存在@Resource或者@Autowired註解的屬性時,該bean在實例化創建完對象後,需要對屬性進行補充,即將@Resource或者@Autowired註解的屬性通過反射的方式,指向另外的bean對象。在Dubbo IOC裏,同樣是類似的實現。首先,會過濾掉那些非setXxx()的方法,只對setXxx()方法處理。這種處理方式,就是截取set後面的字符,例如,存在這樣一個setHitInformService (HitInformService hitInformService)方法,那麼就會截取set後面的字符,並且對截取後的第一個字符做小寫處理,得到“hitInformService”。注意一點是,同時,會獲取參數裏的類型HitInformService,如果類型爲數組、String、Boolean、Character、Number、Date其中一個,則不會對其進行注入操作。反之,就會繼續往下執行。
/**
* Dubbo IOC目前僅支持setter方式注入
* @param instance
* @return
*/
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
//遍歷目標類的所有方法
for (Method method : instance.getClass().getMethods()) {
//檢測方法是否以set開頭,且方法僅有一個參數,且方法訪問級別爲public
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
//獲取setter方法參數類型
Class<?> pt = method.getParameterTypes()[0];
//判斷該對象是否爲數組、String、Boolean、Character、Number、Date類型,若是,則跳出本次循環,繼續下一次循環
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//獲取屬性名,比如 setName方法對應屬性名name
String property = getSetterProperty(method);
/**
* objectFactory 變量的類型爲 AdaptiveExtensionFactory,
* AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用於存儲其他類型的 ExtensionFactory。
*
* Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。
* 前者用於創建自適應的拓展,後者是用於從 Spring 的 IOC 容器中獲取所需的拓展。
*
*/
//從ObjectFactory中獲取依賴對象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//通過反射調用setter方法依賴
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;
}
執行 Object object = objectFactory.getExtension(pt, property)到行代碼,就是去獲取HitInformService hitInformService引用對應的對象,這裏獲取有兩種方式,一種是通過SpringExtensionFactory去通過getBean(name)走Spring加載bean的方式獲取對象,另一種是通過本文的Dubbo SPI方式,根據name去解析文件裏對應的接口實現類Class反射生成返回。
無論是通過哪種方式,最後都需獲取返回一個對象,然後通過method.invoke(instance, object)反射去執行對應的setXxx()方法,將對象進行屬性注入到前文SPI創建的對象cat裏。
到這裏,就完成了接口Animal對應cat這個實現類的創建了,這個過程,就是Dubbo SPI的底層實現細節。最後,將得到的org.apache.dubbo.test.Cat對象向上指向其接口Animal引用,通過接口就可以調用該實現類重寫的haveBehavior方法了。
Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
cat.haveBehavior();