Dubbo的可擴展機制SPI源碼解析
Demo
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol http = extensionLoader.getExtension("dubbo");
System.out.println(http);
上面這個Demo就是Dubbo常見的寫法,表示獲取"dubbo"對應的Protocol擴展點。Protocol是一個接口。
在ExtensionLoader類的內部有一個static的ConcurrentHashMap,用來緩存某個接口類型所對應的ExtensionLoader實例
ExtensionLoader
ExtensionLoader表示一個擴展點加載器,在這個類中除開有上文的Map外,還有兩個非常重要的屬性:
1. Class<?> type:表示當前ExtensionLoader實例是哪個接口的擴展點加載器
2. ExtensionFactory objectFactory:表示當前ExtensionLoader實例的擴展點生成器(一個擴展點,就是一個接口的實現類的對象)
構造方法如下:
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
從上面的構造方法中可以看到一段比較特殊的代碼:
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
在ExtensionLoader中有三個常用的方法:
1. getExtension("dubbo"):表示獲取名字爲dubbo的擴展點實例
2. getAdaptiveExtension():表示獲取一個自適應的擴展點實例
3. getActivateExtension(URL url, String[] values, String group):表示一個可以被url激活的擴展點實例,後文詳細解釋
其中,什麼是自適應的擴展點實例?它其實就是當前這個接口類型的一個代理類,可以通過這個代理類去獲取某個名字的擴展點。那爲什麼要這麼設計呢?
這是因爲對於某個接口的實現類實例,並不是僅僅只能通過Dubbo框架來生成,比如通過getExtension(“dubbo”)方法所獲得擴展點實例,是由Dubbo框架
根據名字對應的實現類幫我們生成的一個實例對象。而有的時候,我們需要從Dubbo之外去獲取實例對象,比如從Spring容器中根據名字取獲取bean,來作爲一個擴展點實例。
所以ExtensionFactory表示一個擴展點工廠,在Dubbo中有三個實現類:
- AdaptiveExtensionFactory:負責從SpiExtensionFactory或SpringExtensionFactory中得到擴展點實例對象
- SpiExtensionFactory:利用Dubbo的Spi機制獲取一個擴展點實例
- SpringExtensionFactory:從Spring的ApplicationContext中獲取bean作爲一個擴展點實例
所以回到上文的那麼代碼,它拿到的就是一個AdaptiveExtensionFactory實例, objectFactory表示一個擴展點實例工廠。
getExtension(String name)方法
在調用getExtension去獲取一個擴展點實例後,會對實例進行緩存,下次再獲取同樣名字的擴展點實例時就會從緩存中拿了。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 獲取默認擴展類
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 創建擴展實例
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension(String name)方法
在調用createExtension(String name)方法去創建一個擴展點實例時,要經過以下幾個步驟:
- 根據name找到對應的擴展點實現類
- 根據實現類生成一個實例,把實現類和對應生成的實例進行緩存
- 對生成出來的實例進行依賴注入(給實例的屬性進行賦值)
- 對依賴注入後的實例進行AOP(Wrapper),把當前接口類的所有的Wrapper全部一層一層包裹在實
例對象上,沒包裹個Wrapper後,也會對Wrapper對象進行依賴注入 - 返回最終的Wrapper對象
private T createExtension(String name) {
// 獲取擴展類
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 實例緩存
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) {
// 生成一個Wrapper實例(傳入了instance),然後進行依賴注入
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
getExtensionClasses
getExtensionClasses()是用來加載當前接口所有的擴展點實現類的,返回一個Map。之後可以從這個Map中按照指定的name獲取對應的擴展點實現類。
當把當前接口的所有擴展點實現類都加載出來後也會進行緩存,下次需要加載時直接拿緩存中的。
Dubbo在加載一個接口的擴展點時,思路是這樣的:
- 根據接口的全限定名去META-INF/dubbo/internal/目錄下尋找對應的文件,調用loadResource方法進行加載
- 根據接口的全限定名去META-INF/dubbo/目錄下尋找對應的文件,調用loadResource方法進行加載
- 根據接口的全限定名去META-INF/services/目錄下尋找對應的文件,調用loadResource方法進行加載
這裏其實會設計到老版本兼容的邏輯,不解釋了。
/**
* 加載當前ExtensionLoader對象中指定的接口的所有擴展
* @return
*/
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
/**
* synchronized in getExtensionClasses
* */
private Map<String, Class<?>> loadExtensionClasses() {
// cache接口默認的擴展類
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
loadResource方法
loadResource方法就是完成對文件內容的解析,按行進行解析,會解析出"=“兩邊的內容,”="左邊的內容就是擴展點的name,
右邊的內容就是擴展點實現類,並且會利用ExtensionLoader類的類加載器來加載擴展點實現類。
然後調用loadClass方法對name和擴展點實例進行詳細的解析,並且最終把他們放到Map中去。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
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) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加載類,並添加到extensionClasses中
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} 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);
}
}
loadClass方法
loadClass方法會做如下幾件事情:
- 當前擴展點實現類上是否存在@Adaptive註解,如果存在則把該類認爲是當前接口的默認自適應類(接口代理類),並把該類存到cachedAdaptiveClass屬性上。
- 當前擴展點實現是否是一個當前接口的一個Wrapper類,如果判斷的?就是看當前類中是否存在一個構造方法,該構造方法只有一個參數,參數類型爲接口類型,如果存在這一的
構造方法,那麼這個類就是該接口的Wrapper類,如果是,則把該類添加到cachedWrapperClasses中去, cachedWrapperClasses是一個set。 - 如果不是自適應類,或者也不是Wrapper類,則判斷是有存在name,如果沒有name,則報錯。
- 如果有多個name,則判斷一下當前擴展點實現類上是否存在@Activate註解,如果存在,則把該類添加到cachedActivates中,cachedWrapperClasses是一個map。
- 最後,遍歷多個name,把每個name和對應的實現類存到extensionClasses中去,extensionClasses就是上文所提到的map。
至此,加載類就走完了。
回到createExtension(String name)方法中的邏輯,當前這個接口的所有擴展點實現類都掃描完了之後,就可以根據用戶所指定的名字,找到對應的實現類了,然後進行實例化,然後進行IOC(依賴注入)和AOP。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
// 是一個Wrapper類
cacheWrapperClass(clazz);
} else {
// 需要有無參的構造方法
clazz.getConstructor();
// 在文件中沒有name,但是在類上指定了Extension的註解上指定了name
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 緩存一下被Activate註解了的類
cacheActivateClass(clazz, names[0]);
// 有多個名字
for (String n : names) {
// clazz: name
cacheName(clazz, n);
// name: clazz
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
Dubbo中的IOC
- 根據當前實例的類,找到這個類中的setter方法,進行依賴注入
- 先分析出setter方法的參數類型pt
- 在截取出setter方法所對應的屬性名property
- 調用objectFactory.getExtension(pt, property)得到一個對象,這裏就會從Spring容器或通過DubboSpi機制得到一個對象,比較特殊的是,如果是通過DubboSpi機制得到的
對象,是pt這個類型的一個自適應對象。 - 再反射調用setter方法進行注入
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
// 利用set方法注入
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// set方法中的參數類型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 得到setXxx中的xxx
String property = getSetterProperty(method);
// 根據參數類型或屬性名,從objectFactory中獲取到對象,然後調用set方法進行注入
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;
}
Dubbo中的AOP
dubbo中也實現了一套非常簡單的AOP,就是利用Wrapper,如果一個接口的擴展點中包含了多個Wrapper類,那麼在實例化完某個擴展點後,就會利用這些Wrapper類對這
個實例進行包裹,比如:現在有一個DubboProtocol的實例,同時對於Protocol這個接口還有很多的Wrapper,比如ProtocolFilterWrapper、ProtocolListenerWrapp
er,那麼,當對DubboProtocol的實例完成了IOC之後,就會先調用new ProtocolFilterWrapper(DubboProtocol實例)生成一個新的Protocol的實例,再對此實例進行IO
C,完了之後,會再調用new ProtocolListenerWrapper(ProtocolFilterWrapper實例)生成一個新的Protocol的實例,然後進行IOC,從而完成DubboProtocol實例的AOP。
AdaptiveClass
上文多次提到了Adaptive,表示一個接口的自適應類,這裏詳細的來講講。
通過getAdaptiveExtension方法獲得的實例就是Adaptive類的實例,在Dubbo中有兩種方式來針對某個接口得到一個Adaptive類,一種是在某個接口的實現類上指定一
個@Adaptive註解,那麼該類就是這個接口的Adaptive類,或者利用Dubbo的默認實現來得到一個Adaptive類,一個接口只能有一個Adaptive類。
如果是手動實現的Adaptive類,那麼自適應邏輯就是自己實現的。如果是有Dubbo默認實現的,那麼我們就看看Dubbo是如何實現Adaptive類的。
createAdaptiveExtensionClass方法
createAdaptiveExtensionClass方法就是Dubbo中默認生成Adaptive類實例的邏輯。說白了,這個實例就是當前這個接口的一個代理對象。比如下面的代碼:
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getAdaptiveExtension();
這個代碼就是Protocol接口的一個代理對象,那麼代理邏輯就是在new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()方法中。
- type就是接口
- cacheDefaultName就是該接口默認的擴展點實現的名字
看個例子,Protocol接口的Adaptive類:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
可以看到,Protocol接口中有四個方法,但是隻有export和refer兩個方法進行代理。爲什麼?因爲Protocol接口中在export方法和refer方法上加了@Adaptive註解。
但是,不是只要在方法上加了@Adaptive註解就可以進行代理,還有其他條件,比如:
1. 該方法如果是無參的,那麼則會報錯
2. 該方法有參數,可以有多個,並且其中某個參數類型是URL,那麼則可以進行代理
3. 該方法有參數,可以有多個,但是沒有URL類型的參數,那麼則不能進行代理
4. 該方法有參數,可以有多個,沒有URL類型的參數,但是如果這些參數類型,對應的類中存在getUrl方法(返回值類型爲URL),那麼也可以進行代理
所以,可以發現,某個接口的Adaptive對象,在調用某個方法時,是通過該方法中的URL參數,通過調用ExtensionLoader.getExtensionLoade
r(com.luban.Car.class).getExtension(extName);得到一個擴展點實例,然後調用該實例對應的方法。
Activate擴展點
上文說到,每個擴展點都有一個name,通過這個name可以獲得該name對應的擴展點實例,但是有的場景下,希望一次性獲得多個擴展點實例,可以通過傳入多個name來獲取,可以通過識別URL上的信息來獲取:
extensionLoader.getActivateExtension(url, new String[]{“car1”, “car2”}); 這個可以拿到name爲car1和car2的擴展類實例,同時還會通過傳入的url尋找可用的擴展類, 怎麼找的呢?
在一個擴展點類上,可以添加@Activate註解,這個註解的屬性有:
1. String[] group():表示這個擴展點是屬於拿組的,這裏組通常分爲PROVIDER和CONSUMER,表示該擴展點能在服務提供者端,或者消費端使用
2. String[] value():指示的是URL中的某個參數key,當利用getActivateExtension方法來尋找擴展點時,如果傳入的url中包含的參數的所有key中,包括了當前擴展點中的
value值,那麼則表示當前url可以使用該擴展點。