在上一篇文章中,我們瞭解了Dubbo中一個普通的擴展類是如何加載的。今天,我們繼續Dubbo SPI的旅程,來看一看兩個特殊的擴展:自激活擴展和自適應擴展。
自激活擴展
什麼是自激活擴展?
如果一個擴展點有很多擴展實現,而我們需要根據不同的條件參數來動態選擇可以使用的擴展時(可能需要同時使用多個擴展),就會使用到自激活擴展了。
這樣說你可能還是不太清楚,我們舉個例子:
你應該或多或少都聽過Filter的概念吧?衆多的語言概念中都有過濾器的概念,Dubbo中也有。通過各種各樣的過濾器能夠幫助我們實現請求的過濾、日誌的打印、流量的統計等一系列功能。但是這麼多的過濾器,並不是在每一種場景下都是要全部加載進來的。我們需要能夠根據請求的參數,自動激活要加載的過濾器。這樣的功能,就是通過自激活擴展來實現。
自激活擴展使用@Activate來標記。
@Activate的使用
@Activate註解可以使用在類、接口、枚舉類和方法上。其註解源碼如下
Documented
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
String[] group() default {};
String[] value() default {};
String[] before() default {};
String[] after() default {};
int order() default 0;
}
通過源碼,可以清楚的看到@Activate註解支持傳入的參數類型。
- group和value代表激活的條件,group代表URL中的分組,value代表URL中的key。如果匹配條件,則會激活擴展。
- before、after和order指定了擴展使用的順序。before和after可以指定在特定的擴展前/後加載使用;order爲整型,表示的是直接的排序信息。
具體的@Activate使用示例,請直接移步官網,這裏不再贅述
自激活擴展如何實現自激活
在真正瞭解自激活擴展的加載過程前,我們先來自己想一想,如何實現一個擴展類的自激活?或者說獲取一個自激活擴展類,我們要做哪些事情?
根據上述的描述,你應該知道,自激活擴展加載最終結果會返回一個擴展類集合。
對於每個擴展類的加載,使用上一節我們瞭解的知識就可以加載。
所以,對於自激活擴展來說,我們額外要做的,就是對於擴展類集合的“激活”。
對於“激活”而言,我們主要需要:
- 能夠根據條件,過濾篩選出需要的擴展類集合
- 能夠根據條件,對篩選後的擴展類集合進行排序
這樣“激活”過的擴展類集合,應當就是符合我們要求的自激活擴展了。
下面我們來看一下,Dubbo中是如何做的吧。
自激活擴展的加載原理
下面我們從源碼角度來看一下自激活擴展是如何加載的。
首先,咱們還是從Dubbo中實際調用自激活擴展的一個實例說起(ProtocolFilterWrapper類)
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
getExtensionLoader方法已經在上一篇《一個普通的擴展類加載》中講解過了,這裏就不再贅述了。主要看一下getActivateExtension是如何處理的。
public List<T> getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
這裏主要也是進行一個方法參數的透傳,具體涉及到了Dubbo中的URL參數key的解析,這裏先不用太關注,具體還是進入實際調用方法中來看。
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
// $-- 如果擴展點名稱列表中不含-default選項,則加載全部默認的自激活擴展點,篩選加入自激活擴展集合中
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
// $-- 生成擴展類
getExtensionClasses();
// $-- 遍歷緩存中的默認自激活擴展
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Activate activate = entry.getValue();
// $-- 如果緩存中的自激活擴展類匹配條件,則加入自激活擴展集合中
// $-- 1. group匹配
if (isMatchGroup(group, activate.group())) {
T ext = getExtension(name);
// $-- 2. 該默認擴展點不在要獲取的擴展點集合中(下面會專門處理要獲取的擴展點) && 要獲取的擴展點集合不存在該默認擴展點的自反 && 此擴展點的value與url匹配
if (!names.contains(name)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) {
exts.add(ext);
}
}
}
// $-- 按照@Activate配置的before、after、order等參數進行排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
// $-- 根據要獲取的擴展點名稱列表,獲取擴展點,進行過濾篩選,加入自激活擴展集合中
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
// $-- 如果擴展點名稱不是以-開頭 && 擴展點集合中沒有該擴展點的“自反”(如:既含有name,又含有-name,這樣自相矛盾的不處理)
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
if (Constants.DEFAULT_KEY.equals(name)) {
// $-- 如果擴展點名稱爲default,則將臨時自激活擴展集合轉移到自激活擴展集合首位(用於排序,這樣default就排到後面了)
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
// $-- 加載擴展點,並放入臨時自激活擴展集合中
T ext = getExtension(name);
usrs.add(ext);
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
此處的getActivateExtension方法是自激活擴展的主體邏輯,主要的處理邏輯是對自激活擴展點的獲取、篩選和排序。
獲取擴展點的邏輯,我們已經在上一篇文章中介紹了,這裏不再贅述。另外說明一下,這裏的自激活擴展點緩存cachedActivates是在文件讀取加載擴展類時,通過loadClass方法“順便”緩存下來的。
對於自激活擴展的篩選和排序,這裏分爲了兩種場景。一種是系統默認的擴展,一種是通過URL參數傳入的用戶自定義擴展。
- 對於系統默認的擴展。
- 篩選:如果參數中沒有-default選項,則加載默認的@Activate擴展。這裏得到的所有默認@Activate擴展點,均需要根據傳入參數的group、url配置的key等進行匹配篩選。對此,註釋中已經有很好的說明了。
- 排序:默認@Activate擴展點的排序,是按照@Activate配置的before、after、order等參數進行排序,排序的邏輯在ActivateComparator中,這裏不贅述
- URL指定的擴展
- 篩選:由於擴展點名稱是作爲參數傳遞進來的,因此需要進行擴展是否加載的邏輯判斷(如:“-”開頭的擴展不被加載,如“-filter1”代表不加載默認filter1擴展)
- 排序:對於URL參數傳入的擴展點,用戶可以通過修改URL中參數中擴展點順序來調整擴展點的激活順序。
舉個例子,如果URL爲 dubbo://localhost/test?ext=filter1,default,則擴展點ext的激活順序爲先filter1再default(URL參數中的“default”代表默認的自激活擴展)
到這裏處理完成,就能獲取指定的條件下(URL)可以使用的自激活擴展列表了。不知道與你想象的實現有多少出入呢?😄
自適應擴展
下面,讓我們來了解Dubbo中使用非常廣泛的另一種擴展——自適應擴展。
什麼是自適應擴展
一個擴展接口會有多種實現類,具體使用哪個實現類並不是寫死在配置或代碼中的,而是在運行時,通過URL參數來動態確定的。這種擴展就叫做自適應擴展。
自適應擴展使用@Adaptive來標註。
@Adaptive的使用
Adaptive可以用在類和接口方法上。
- 標記在類上,則代表該類爲默認的擴展點實現。如:AdaptiveCompiler爲Compiler的默認實現
- 標記在方法上,則代表擴展點需要根據參數動態生成。如Dispatcher接口:
SPI(AllDispatcher.NAME)
public interface Dispatcher {
@Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"})
ChannelHandler dispatch(ChannelHandler handler, URL url);
}
Dispatcher接口的dispatch方法上標註了@Adaptive註解,代表這個方法調用時,會自動尋找系統中匹配的擴展類。匹配時會根據@Adaptive註解的value值進行選擇,順序依次爲 常量 Constants.DISPATCHER_KEY,dispatcher,channel.handler。按照順序,只要匹配上某一個擴展類就直接返回,調用該擴展類的dispatch方法。
這裏簡化了一下匹配的規則,方便理解。具體的規則,會在下方源碼分析的時候介紹。
自適應擴展如何實現自適應
在正式的源碼解讀之前,我們不妨就自適應擴展的這種表現,思考一下,如何實現一個自適應擴展?
自適應擴展依然是一個擴展,所以我們理論上依然可以使用Dubbo SPI的普通擴展來加載各種各樣的擴展。但是如何實現自適應呢?
自適應(這裏主要討論@Adaptive註解標註在方法上的自適應)要求能夠根據不同的擴展點,實現不同的匹配規則,來進行擴展點的選擇,最後再來調用選中擴展類的該方法。
拿上述Dispatcher爲例,爲了實現擴展點選擇,應該有這樣一段基本代碼
ChannelHandler dispatch(ChannelHandler handler, URL url) {
String extName = url.getParameter("dispatcher", url.getParameter("dispather", url.getParameter("channel.handler", "all")));;
Dispatcher dispatcher = ExtensionLoader.getExtensionLoader(Dispatcher.class).getExtension(extName);
return dispatcher.dispatch(handler, url);
}
很顯然,這樣的代碼是特殊的,無法公用的。Dubbo顯然不會爲每一個擴展點寫這樣一段代碼。那麼如何生成這樣的代碼呢?
自然而然的,我們想到可以使用javassist、cglib、jdk動態代理來生成這樣的“自適應代碼”,從而實現公用、方便擴展。事實上,Dubbo確實也這麼做了,接下來讓我們從源碼角度來看一下Dubbo自適應擴展是如何玩起來的。
自適應擴展的加載原理
按照慣例,我們依然從一個簡單的調用開始我們的源碼分析。
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
這行代碼先是獲取Protocol的擴展加載器,然後使用加載器去獲得自適應的擴展。前半段不再贅述,讓我們將目光轉到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;
}
這段代碼主要還然是緩存的老套路,緩存的是自適應擴展類的實例。真正的創建自適應擴展要看createAdaptiveExtension方法。
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
創建自適應擴展的方法依然是一個嵌套調用。先通過getAdaptiveExtensionClass生成自適應擴展類Class,然後通過反射實例化,最後通過injectExtension方法進行setter擴展注入。
injectExtension方法我們已經在上一篇解讀過了,這裏不再贅述。來看一下getAdaptiveExtensionClass方法。
private Class<?> getAdaptiveExtensionClass() {
// $-- 加載所有擴展類
getExtensionClasses();
// $-- 如果緩存有默認的自適應擴展類,則直接返回(這裏主要是@Adaptive標記在擴展類上的場景,該擴展類爲該擴展點的默認擴展類)
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// $-- 生成自適應擴展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
通過getExtensionClasses方法加載所有擴展類也已經在上一篇瞭解過了,這裏不再贅述。
通過cachedAdaptiveClass緩存的判斷,體現了@Adaptive標記在擴展類上即爲默認擴展類的這一設計。cachedAdaptiveClass是在從文件讀取加載擴展類,loadClass時賦值的。
下面還是像“套娃”一樣,進入createAdaptiveExtensionClass方法
private Class<?> createAdaptiveExtensionClass() {
// $-- 生成自適應擴展類的代碼
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
// $-- 獲取Compiler的擴展點實現類,編譯生成的代碼爲Class
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
從這段代碼裏我們已經可以大致驗證自適應的猜想了。
首先通過createAdaptiveExtensionClassCode方法,生成該擴展點的“自適應代碼”。
然後獲取類加載器和編譯器。利用編譯器編譯得到最終的自適應擴展類。
需要注意的是這裏的編譯器Compiler依然是一個自適應擴展類。那是否就構成了一個死循環了呢?
當然不可能,爲了不打擾主體流程,具體原因放在最後討論。這裏先知道能否獲取到Compiler,並且默認爲javassist擴展。
讓我們繼續看一下代碼是如何生成的吧!且看createAdaptiveExtensionClassCode方法
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuilder = new StringBuilder();
Method[] methods = type.getMethods();
// $-- 校驗是否有@Adaptive註解(至少應當有一個方法有Adaptive註解)
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
// $-- 生成包、引用等代碼
codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
// $-- 遍歷生成方法代碼
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) {
// $-- 如果該方法沒有@Adaptive註解,則生成拋異常代碼
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
// $-- 如果該方法有@Adaptive註解,即爲自適應方法
// $-- 判斷參數中是否有URL
int urlTypeIndex = -1;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// found parameter in URL type
if (urlTypeIndex != -1) {
// $-- 生成校驗URL參數不能爲空代碼
// 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);
}
// did not find parameter in URL type
else {
// $-- 方法參數中沒有直接URL類型參數
String attribMethod = null;
// find URL getter method
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
// $-- 嘗試遍歷方法參數列表中每一個類中是否有滿足要求的類(get開頭,返回URL,無參數,公共靜態)
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 adaptive 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();
// value is not set, use the value generated from class name as the key
if (value.length == 0) {
// $-- "駝峯規則"匹配,如 YyyInvokerWrapper ==> yyy.invoker.wrapper
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()};
}
// $-- 生成Invocation類空校驗代碼
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(");");
}
// $-- 補齊生成方法的頭尾代碼
codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
for (int i = 0; i < pts.length; i++) {
if (i > 0) {
codeBuilder.append(", ");
}
codeBuilder.append(pts[i].getCanonicalName());
codeBuilder.append(" ");
codeBuilder.append("arg").append(i);
}
codeBuilder.append(")");
if (ets.length > 0) {
codeBuilder.append(" throws ");
for (int i = 0; i < ets.length; i++) {
if (i > 0) {
codeBuilder.append(", ");
}
codeBuilder.append(ets[i].getCanonicalName());
}
}
codeBuilder.append(" {");
codeBuilder.append(code.toString());
codeBuilder.append("\n}");
}
codeBuilder.append("\n}");
if (logger.isDebugEnabled()) {
logger.debug(codeBuilder.toString());
}
return codeBuilder.toString();
}
這裏不得不吐槽一下,這個方法實在是太長了。。。😂
簡單來說,爲了拼接出自適應的代碼,做了這樣幾件事情:
- 校驗該擴展點的方法是否有@Adaptive註解。至少有一個方法有@Adaptive註解,這樣纔有生成自適應擴展的必要
- 通過拼接字符串的方式,生成包、引用等代碼
- 遍歷該擴展點所有的方法,生成方法代碼
3.1 如果該方法上沒有@Adaptive註解,則不允許該方法自適應調用,生成拋異常代碼
3.2 如果方法上有@Adaptive註解,則需要生成自適應方法 - 類所有代碼的拼接
其中,生成自適應方法主要邏輯如下:
- 生成獲取默認擴展類的代碼
- 生成判斷擴展類爲空的代碼
- 生成擴展類方法調用的代碼
整個邏輯還是拼接代碼的套路,需要注意的就是擴展類的獲取邏輯。自適應代碼生成邏輯中大量的代碼都是在處理這個邏輯。
擴展類依然還是通過ExtensionLoader.getExtension(extName)來生成的,唯一要注意的是這裏擴展點名稱extName的取值邏輯。
擴展點名稱extName是從URL中的鍵值對獲取的。那麼URL中的鍵key又是如何獲取的呢?這裏簡單總結如下:
- 先取@Adaptive註解中的key
- 如果@Adaptive註解未配置key,則會根據當前擴展點的名稱,生成key(“駝峯規則”,如YyyInvokerWrapper ==> yyy.invoker.wrapper)
- 如果依然沒有找到,則取擴展點@SPI註解中的key
走到這裏,我們終於把自適應擴展關鍵的代碼生成邏輯講解完成了。剩下的就是使用編譯器來進行編譯了,編譯過程就不贅述了。
這裏我們遺留了一個問題,Compiler也是一個自適應擴展,那麼走到這裏會不會形成一個死循環呢?讓我們繼續看一下Compiler自適應擴展是如何加載的
Compiler自適應擴展類是如何加載的
Compiler自適應擴展會不會形成死循環呢?
答案當然是NO!因爲Compiler擴展點的自適應擴展類並不是通過生成代碼而獲取的。
不知你是否還記得,如果@Adaptive標記在類上時,表示這個類是該擴展點的默認實現類。而Compiler就是利用這一點進行加載的。
在AdaptiveCompiler類上有@Adaptive標識,代表AdaptiveCompiler爲Compiler默認的擴展實現
@Adaptive
public class AdaptiveCompiler implements Compiler {
private static volatile String DEFAULT_COMPILER;
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}
@Override
public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
}
}
在我們的應用程序中未指定DEFAULT_COMPILER的情況下,默認會使用默認擴展(javassist)作爲compiler。看loader.getDefaultExtension方法
public T getDefaultExtension() {
getExtensionClasses();
if (null == cachedDefaultName || cachedDefaultName.length() == 0
|| "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}
getDefaultExtension方法會取cachedDefaultName作爲默認擴展類名。那麼這個cachedDefaultName又是從哪裏獲取的呢?
這就要追溯到文件中讀取加載擴展類的過程了。在真正查找文件之前,會有一個SPI註解的判斷,具體實現在loadExtensionClasses方法中
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
// $-- @SPI註解只允許指定一個默認值
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// $-- @SPI指定了一個默認值,緩存起來
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// $-- 加載擴展類資源目錄
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
...
}
如果擴展點@SPI註解的value有且只有一個值,那麼這個值就是默認擴展類。我們再看Compiler接口
@SPI("javassist")
public interface Compiler {
Class<?> compile(String code, ClassLoader classLoader);
}
你看,這樣就指定javassist爲默認擴展類了。
在調用getAdaptiveExtension獲取自適應擴展時,判斷到如果有默認的自適應擴展,就會直接返回該擴展類。
private Class<?> getAdaptiveExtensionClass() {
// $-- 加載所有擴展類
getExtensionClasses();
// $-- 如果緩存有默認的自適應擴展類,則直接返回(這裏主要是@Adaptive標記在擴展類上的場景,該擴展類爲該擴展點的默認擴展類)
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// $-- 生成自適應擴展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
總結
到此爲止,Dubbo SPI機制主要內容就已經介紹完成了。
Dubbo SPI機制是理解後續服務暴露、引用和調用過程的基石,也是整個Dubbo能夠博採衆長,吸納衆多開源解決方案的一個重要原因。
整個流程可能有點繞,但就我而言,通過實際Debug對於理解這套機制非常有用。
不知你對Dubbo SPI瞭解有多少了呢?😁