Dubbo(一)-SPI(2) 機制之 Dubbo 的 SPI
Dubbo 的 SPI 是什麼
Dubbo 的擴充套件點載入從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。
Dubbo 改進了 JDK 標準的 SPI 的以下問題:
- JDK 標準的 SPI 會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
- 如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取指令碼型別的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類載入失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當使用者執行 ruby 指令碼時,會報不支援 ruby,而不是真正失敗的原因。
- 增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。
Dubbo 的 SPI 核心檔案目錄
沒錯就只有那麼一點,是不是覺得很神奇。具體介紹內容前,先介紹下檔案作用註解部分
-
@SPI
該註解是標記介面是擴充套件介面,也就是說想要使用 Dubbo 的 SPI 就必須打上這個註解。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface SPI { /** * default extension name */ String value() default ""; } 複製程式碼
value 的作用是標記 spi 擴充套件所需要的對應類的。和上篇 javaSpi 作用其實是一樣的,只不過 dubbo 弄了個標示可以選擇不同的值,以 xxx=xxxx 的形式選擇
e.g:
-
@Adaptive
該註解是整個功能的核心,它的作用有兩個:
- 作用在類上面,這代表該實現類是唯一承認的介面實現類,也就是哪怕我在@SPI 註解上指定了對應的實現類,也是不認噠
- 作用在方法上(這個需要和@SPI 註解連用),而且引數中必須有一個 org.apache.dubbo.common.URL 才行,Dubbo 會對應生成一個 xxx$Adaptive 類,作為一箇中間類,用於選擇提供不同的實現類。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; } 複製程式碼
-
@Activate
該註解是擴充套件點自動啟用載入的註解,就是用條件來控制該擴充套件點實現是否被自動啟用載入,在擴充套件實現類上面使用。
介面實現部分
-
ExtensionFactory
SPI 利用工廠模式來實現各功能,其中前言也講到過,Dubbo 的 SPI 增加了對擴充套件點 IoC 和 AOP 的支援,這個工廠也是實現這個的基礎,它有 3 個實現類:AdaptiveExtensionFactory,SpiExtensionFactory 和 SpringExtensionFactory。同樣該介面也是被@SPI 修飾的,所以它也是可以擴充套件的,但是 AdaptiveExtensionFactory 有@Adaptive 實現,所以它是唯一作用的。它把所有 ExtensionFactory 的實現獲取了,通過遍歷的形式,執行兩種不同的獲取方式,相容了 Spring 框架的形式 ExtensionFactory:
@SPI public interface ExtensionFactory { /** * Get extension. * * @param type object type. * @param name object name. * @return object instance. */ <T> T getExtension(Class<T> type, String name); } 複製程式碼
AdaptiveExtensionFactory:
public AdaptiveExtensionFactory() { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } @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; } 複製程式碼
SpiExtensionFactory:
public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); } } return null; } } 複製程式碼
SpringExtensionFactory:
public <T> T getExtension(Class<T> type, String name) { //SPI should be get from SpiExtensionFactory if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { return null; } for (ApplicationContext context : contexts) { if (context.containsBean(name)) { Object bean = context.getBean(name); if (type.isInstance(bean)) { return (T) bean; } } } logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName()); if (Object.class == type) { return null; } for (ApplicationContext context : contexts) { try { return context.getBean(type); } catch (NoUniqueBeanDefinitionException multiBeanExe) { logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type."); } catch (NoSuchBeanDefinitionException noBeanExe) { if (logger.isDebugEnabled()) { logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe); } } } logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean."); return null; } 複製程式碼
ok 全部 dubbo 的 spi 檔案內容就是如此,我們在看一下執行的細則
Dubbo 的 SPI 執行流程
- 執行入口:
ExtensionLoader<SayWord> loader = ExtensionLoader.getExtensionLoader(SayWord.class);
複製程式碼
呼叫 ExtensionLoader 獲取 ExtensionLoader 物件。這個方法主要是獲取對應 SPI 介面的 ExtemsionLoader。
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 interface!");
}
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
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 註解,則返回異常。如果符合要求,就會在 EXTENSION_LOADERS 物件中獲取,如果已經快取了,則返回快取的物件,如果沒有快取,則新建 new 一個 ExtensionLoader 物件。EXTENSION_LOADERS 是一個 ConcurrentMap<Class, ExtensionLoader>。是一層快取。
來看一下建立一個 ExtensionLoader 物件做了什麼。這是一個私有建構函式哦,只允許內部建立。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
複製程式碼
這裡無非就是設定了 type,objectFactory 物件。一般情況下我們設定 spi 物件都不會是 ExtensionFactory,所以大部分都會走後面邏輯,會優先建立 ExtensionFactory 的 ExtensionLoader 物件。
getAdaptiveExtension()做了啥?:
這個方法其實就是獲取了 spi 對應實現形式,類似的還有 getActivateExtension()。其實這個很好理解,分別對應了我們兩個註解的功能:@Adaptive 和@Activate。Spi 核心還是 getAdaptiveExtension()方法。
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
}
else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
複製程式碼
讓我們來看看具體是啥樣子的:優先判斷快取的 hoder 是否存在,在沒有呼叫過這個方法之前,該物件只是創立了個空物件:
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
複製程式碼
所以第一次獲取的是空。那麼會進入邏輯判斷內部,然後判斷是否存在異常,會把第一次建立的異常會快取進。所以這邊會快取 Adaptive 實現物件或者就是異常物件。 再繼續看,利用了同步鎖二重判斷,來確保併發的安全性,然後呼叫 createAdaptiveExtension()方法來建立。然後快取了結果。下面是 createAdaptiveExtension()實現
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}
複製程式碼
會優先呼叫 getAdaptiveExtensionClass()方法然後例項化。其中流程很簡單,最核心的部分為:getAdaptiveExtensionClass()->getExtensionClasses()->loadExtensionClasses(); loadExtensionClasses()主要是載入spi中的類檔案,這部分就和正常是spi一樣了
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if(names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if(names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
複製程式碼
而在getExtensionClasses()方法中:
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
複製程式碼
如果存在cachedAdaptiveClass就會返回這個,所以這個也就是未什麼@Adaptive註解就是唯一標示了(其中cachedAdaptiveClass的產生在loadFile方法裡。可以看看)
如果沒有cachedAdaptiveClass就會建立一個,
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
複製程式碼
這邊核心的部分就是 createAdaptiveExtensionClassCode()了,來看看裡面實現啥
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuidler = new StringBuilder();
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for(Method m : methods) {
if(m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// 完全沒有Adaptive方法,則不需要生成Adaptive類
if(! hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
codeBuidler.append("package " + type.getPackage().getName() + ";");
codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
for (Method method : methods) {
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes();
Class<?>[] ets = method.getExceptionTypes();
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
int urlTypeIndex = -1;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// 有型別為URL的引數
if (urlTypeIndex != -1) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);
s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
}
// 引數沒有URL型別
else {
String attribMethod = null;
// 找到引數的URL屬性
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
urlTypeIndex = i;
attribMethod = name;
break LBL_PTS;
}
}
}
if(attribMethod == null) {
throw new IllegalStateException("fail to create adative class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
// Null point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);
s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
String[] value = adaptiveAnnotation.value();
// 沒有設定Key,則使用“擴充套件點介面名的點分隔 作為Key
if(value.length == 0) {
char[] charArray = type.getSimpleName().toCharArray();
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < charArray.length; i++) {
if(Character.isUpperCase(charArray[i])) {
if(i != 0) {
sb.append(".");
}
sb.append(Character.toLowerCase(charArray[i]));
}
else {
sb.append(charArray[i]);
}
}
value = new String[] {sb.toString()};
}
boolean hasInvocation = false;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
hasInvocation = true;
break;
}
}
String defaultExtName = cachedDefaultName;
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) {
if(i == value.length - 1) {
if(null != defaultExtName) {
if(!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
else {
if(!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
getNameCode = "url.getProtocol()";
}
}
else {
if(!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
code.append("\nString extName = ").append(getNameCode).append(";");
// check extName == null?
String s = String.format("\nif(extName == null) " +
"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
type.getName(), Arrays.toString(value));
code.append(s);
s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);
// return statement
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}
s = String.format("extension.%s(", method.getName());
code.append(s);
for (int i = 0; i < pts.length; i++) {
if (i != 0)
code.append(", ");
code.append("arg").append(i);
}
code.append(");");
}
codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
for (int i = 0; i < pts.length; i ++) {
if (i > 0) {
codeBuidler.append(", ");
}
codeBuidler.append(pts[i].getCanonicalName());
codeBuidler.append(" ");
codeBuidler.append("arg" + i);
}
codeBuidler.append(")");
if (ets.length > 0) {
codeBuidler.append(" throws ");
for (int i = 0; i < ets.length; i ++) {
if (i > 0) {
codeBuidler.append(", ");
}
codeBuidler.append(pts[i].getCanonicalName());
}
}
codeBuidler.append(" {");
codeBuidler.append(code.toString());
codeBuidler.append("\n}");
}
codeBuidler.append("\n}");
if (logger.isDebugEnabled()) {
logger.debug(codeBuidler.toString());
}
return codeBuidler.toString();
}
複製程式碼
那麼大一串其實就是生成了一個class的程式碼。這邊看一下生成的樣子是啥樣子的
package com.pangxie.server.dubbo.dubbo.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
@Override
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word", "en");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord) ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼
其中,標示了@SPI中的value值就會變成這邊的預設值。也就是如果沒有設計URL內容,那麼返回的都是預設spi的介面。 所以總共的流程就是如此了。接下來看看不同情況的執行結果。在來理解下生成的class程式碼的執行。
Dubbo 的 xxx$Adpative 不同執行
測試程式碼如下
介面:
@SPI
public interface SayWord {
@Adaptive
String saySomething(String message,URL url);
}
複製程式碼
實現1:
public class SayChineseWord implements SayWord {
@Override
public String saySomething(String message, URL url) {
return "你好啊";
}
}
複製程式碼
實現2:
public class SayEnglishWord implements SayWord {
@Override
public String saySomething(String message, URL url) {
return "Hello";
}
}
複製程式碼
配置檔案:
cn=com.pangxie.server.dubbo.dubbo.spi.impl.SayChineseWord
en=com.pangxie.server.dubbo.dubbo.spi.impl.SayEnglishWord
複製程式碼
執行入口:
public class Main {
public static void main(String[] args) {
ExtensionLoader<SayWord> loader = ExtensionLoader.getExtensionLoader(SayWord.class);
SayWord sayWord=loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test");
System.out.println(sayWord.saySomething("d", url));
}
}
複製程式碼
- @Adaptive沒有預設值,@SPI沒有預設值,URL沒有值
package com.pangxie.server.dubbo.dubbo.spi.api;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord)ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼
生成的程式碼為上訴所示;其實結果我們已經可以猜想到,spi沒有預設值,url中也沒有標示。所以結果肯定是啥都找不到的
Exception in thread "main" java.lang.IllegalStateException: Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(test://localhost/test) use keys([say.word])
at com.pangxie.server.dubbo.dubbo.spi.api.SayWord$Adpative.saySomething(SayWord$Adpative.java)
at com.pangxie.server.dubbo.dubbo.spi.Main.main(Main.java:33)
複製程式碼
- @Adaptive沒有預設值,@SPI沒有預設值,URL有值
package com.pangxie.server.dubbo.dubbo.spi.api;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord)ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼
生成程式碼如上訴所示。猜猜看結果,
你好啊
Disconnected from the target VM, address: '127.0.0.1:64482', transport: 'socket'
Process finished with exit code 0
複製程式碼
輸出了我們以cn為主的實現類的結果。所以如果沒有設定預設值,我們可以利用URL設定的方式,來進行選擇,而且這邊也有一個好處,就是可以動態選擇。只需要通過設定URL的值,就可以避免編碼修改的方式,比原來的靈活十足。
- @Adaptive沒有預設值,@SPI有預設值,URL有值 設定SPI預設值為en,URL為cn
package com.pangxie.server.dubbo.dubbo.spi.api;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word", "en");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord)ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼
生成原始碼如上述所示
你好啊
Disconnected from the target VM, address: '127.0.0.1:64614', transport: 'socket'
Process finished with exit code 0
複製程式碼
最終結果以url的key值為主哦。
總結
該文章講解了dubbo的SPI擴充套件機制的實現原理,最關鍵的是弄清楚dubbo跟jdk在實現SPI的思想上做了哪些改進和優化,解讀dubbo SPI擴充套件機制最關鍵的是弄清楚@SPI、@Adaptive、@Activate三個註解的含義,大部分邏輯都被封裝在ExtensionLoader類中。dubbo的spi學習下來讓我感覺一點就是靈活性是在是太突出了,不僅僅是根據註解,也可以根據引數形式,利用建立中間包裝類,使用裝飾模式,來加強spi的靈活。根據上一節其實我們可以知道dubbo選擇spi內容的順序為 @Adaptive》URL》SPI預設值。