前言
不得不說
Dubbo
的自定義spi
減輕了擴展者的負擔,但減輕負擔的代價是使用大量晦澀難懂的代碼,調用層次深,使閱讀者步步維艱。爲了避免後來者再像我當初那樣看源碼的吃力,特意把我的閱讀見解分享下來,希望大家一起學習。
我使用的是 maven
倉庫最新的 dubbo2.6.5
的源碼,在社區看到要 2.7
版本就要出了。爲了更方便的學習,我們可以等這個版本掌握差不多了再看更高的版本。
SPI
究竟什麼是
SPI
呢,SPI
的英文名爲Service Provider Interface
顧名思義服務提供者接口,是面向開發者的。使用SPI
我們可以把接口實現的全限定名放在文件中,通常在META-INF/services
目錄下,文件名爲接口名,文件內容爲接口的實現類。由服務器加載讀取配置文件,加載實現類,這樣在運行時動態爲接口替換實現類。正因此特性,我們可以很容易的通過SPI
機制爲我們的程序提供拓展功能。
其實 JDK
自帶了 SPI
功能。
JDK SPI
接口及其實現類
public interface Echo {
void echo();
}
public class DubboEcho implements Echo {
@Override
public void echo() {
System.out.println("I am dubbo");
}
}
public class JdkEcho implements Echo {
@Override
public void echo() {
System.out.println("I am jdk");
}
}
資源文件目錄爲:META-INF/services/接口全限定名
比如我的:META-INF/services/com.dfire.spi.Echo
com.dfire.spi.DubboEcho
com.dfire.spi.JdkEcho
測試類:
public class App {
public static void main(String[] args) {
ServiceLoader<Echo> says = ServiceLoader.load(Echo.class);
for (Echo echo : says) {
echo.echo();
}
}
}
執行後輸出結果爲
I am dubbo
I am jdk
目錄結構如下
完成這個例子,大家就能簡單瞭解了 JDK SPI
。如果想要新增接口只需寫個實現類配置在文件中即可。那麼 Dubbo
爲什麼沒有使用JDK SPI
而使用了自定義SPI
呢?
首先說我們能看到的缺點
- 一次性實例化所有的擴展點實現,即使該實現類沒有用上,浪費資源
看不到的缺點
- 如果擴展點加載失敗,連擴展點的名稱都拿不到了。比如:
JDK
標準的ScriptEngine
,通過getName
獲取腳本類型的名稱,但如果RubyScriptEngine
因爲所依賴的jruby.jar
不存在,導致RubyScriptEngine
類加載失敗,這個失敗原因被吃掉了,和ruby
對應不起來,當用戶執行ruby
腳本時,會報不支持ruby
,而不是真正失敗的原因。
Dubbo SPI
Dubbo SPI
除了修復 JDK SPI
的缺點外還增加了AOP
和IOC
功能。
在提供者的入口類 ServiceConfig
中有這樣一行代碼
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
其中 ExtensionLoader
類即是 Dubbo SPI
的實現類。
我們就從這行代碼逐步分析。
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對象
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//創建ExtensionLoader對象
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
- 在前兩行首先是進行判空和該類是否爲接口
- 繼續從緩存中根據接口類型來獲得
ExtensionLoader
對象,如果爲空就創建 - 進入
ExtensionLoader
構造方法
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
哇,好複雜。
簡單分析下
首先判斷 type
是否爲 ExtensionFactory
類,如果是那麼 objectFactory
就爲null
。如果不是就獲得 ExtensionFactory
的自適應擴展類
。ExtensionLoader.getExtensionLoader(T.class).getAdaptiveExtension()
這行代碼其實就是我們剛剛進入分析的入口。下面繼續分析 getAdaptiveExtension
方法。
獲得所有的擴展類
public T getAdaptiveExtension() {
//檢測緩存中是否存在自適應擴展類實現
Object instance = cachedAdaptiveInstance.get();
//double check
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;
}
此方法,主要是爲了獲得自適應擴展類。首先從緩存的 cachedAdaptiveInstance
中檢測是否已經存在。
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
cachedAdaptiveInstance
是 Holder
類創建的一個對象查看一下 Holder
類
public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
發現 Holder
類中持有一個 value
對象並且以 volatile
關鍵字修飾,保證其可見性,指令有序性。
繼續往下分析,常見的 double check
,如果不存在就進入 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);
}
}
createAdaptiveExtension
方法主要做了:調用getAdaptiveExtensionClass
方法獲得自適應擴展類,通過反射 newInstance
新建一個對象,然後使用 injectExtension
方法進入屬性注入操作。
根據調用順序,首先進入 getAdaptiveExtensionClass
方法
private Class<?> getAdaptiveExtensionClass() {
//加載配置文件的所有的類
getExtensionClasses();
//如果cachedAdaptiveClass已經加載過,直接返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//無自適應擴展類,動態創建自適應擴展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
在 getAdaptiveExtensionClass
方法中主要做了兩件事,首先調用 getExtensionClasses
方法,該方法會加載所有配置在文件中的類,其中如果有自適應擴展類,那麼就會爲 cachedAdaptiveClass
賦值。而 createAdaptiveExtensionClass
會在發現沒有自定義的自適應擴展類時進行動態生成。
那麼首先進入 getExtensionClasses
方法
private Map<String, Class<?>> getExtensionClasses() {
//首先從緩存中獲得所有的實現類,如果發現爲null 則進行初始化
Map<String, Class<?>> classes = cachedClasses.get();
//double check
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//爲空 則進行初始化加載類
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
同樣是 double check
,首先根據緩存 map
判斷是否已經加載過,如果未加載過那麼進行第一次加載,並設置緩存。那麼就進入加載擴展類的方法中 loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
//首先獲得接口類的註解@SPI
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
//如果接口類的@SPI註解不爲null
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));
}
//設置默認的實現類名稱爲接口類註釋@SPI的value
//tip:這裏要記住哦!
if(names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
//從 "META-INF/dubbo/internal/" 目錄下解析實現類
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
//從 "META-INF/dubbo/" 目錄下解析實現類
loadFile(extensionClasses, DUBBO_DIRECTORY);
//從 "META-INF/services/" 目錄下解析實現類
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
該方法大致內容是:
- 查看該接口類是否具有
SPI
註解,有的話則獲得註解的值,並賦值給cachedDefaultName
作爲自適應擴展實現的默認值 - 去指定的文件路徑下解析該接口的實現類的配置信息
在看 loadFile
方法之前首先附上 Dubbo SPI
的一個資源文件 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
,方便大家理解。
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
我們可以明顯發現和 Java SPI
的區別,Dubbo
是使用 name=class
的 key-value
模式存儲的實現類,至於這樣有什麼好處,我們繼續進入 loadFile
方法分析
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
//獲得文件的路徑名,比如 type 爲Protocol.class,dir 爲 META-INF/dubbo/internal/
//那麼fileName = "META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol"
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
//根據是否存在類加載器來使用不同的資源加載文件
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//遍歷文件
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
//讀取文件內容
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) {
//加載類
Class<?> clazz = Class.forName(line, true, classLoader);
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//判斷該加載類是否具有 @Adaptive 註解
if (clazz.isAnnotationPresent(Adaptive.class)) {
//如果當前加載類有 @Adaptive 註解並且緩存的自適應註解類爲空 那麼爲緩存設置
if(cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
//如果 cachedAdaptiveClass 緩存已經設置 那麼拋出異常 @Adaptive 放在所有接口實現類的其中一個類上面 即:自適應擴展類只能有一個
} else if (! cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
//如果沒有 @Adaptive 註解
} else {
try {
//首先判斷該加載類是否具一個參數爲自己的構造方法 如果不拋出 NoSuchMethodException 異常,那麼將其加入包裝(裝飾)類集合中
//tip:這裏要記住哦
clazz.getConstructor(type);
//可能有一個或多個
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
//是否有默認(空)構造方法
clazz.getConstructor();
//判斷配置文件中 name 是否爲 null
if (name == null || name.length() == 0) {
//如果爲 null 就根據 type 自動生成一個
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
//分割 name
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
//判斷類上是否有 @Activate 註解 如果有則以第一個 name 作爲 key Activate 對象作爲val 放入緩存中
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
//遍歷所有 name
for (String n : names) {
//判斷緩存的class -> name 映射中是否存在 不存在則添加
if (! cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
//判斷 name -> class 的映射 map 中是否已經添加過,進行添加/拋異常操作
//tip:extensionClasses 是我們在 loadExtensionClasses 方法中創建的 map, 用來存放我們解析的結果
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
代碼很長,需要大家仔細去看,具體的註釋我已經寫的很清楚。
總結一下這段代碼的作用
- 解析在
"META-INF/dubbo/internal/","META-INF/dubbo/" ,"META-INF/services/"
等目錄下的SPI
資源文件 - 發現實現類具有
@Adaptive
註解,即自定義自適應擴展類,則爲緩存cachedAdaptiveClass
賦值,很重要,別忘記我們爲什麼在執行到getAdaptiveExtensionClass
方法時進入getExtensionClasses
方法,就是爲了得到自適應擴展類 - 查看實現類是否具有一個參數爲接口類的構造方法,有的話則把該實現類放入修飾類緩存中
- 查看實現類是否具有
@Activate
註解,存在的話放入緩存 - 將解析
SPI
資源文件 以class -> name
的方式 放入緩存map
的cachedNames
中
加載 SPI
資源這一部分算是結束了,那麼我們繼續回到 getAdaptiveExtensionClass
方法中。
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
我們通過上面分析知道 getExtensionClasses
方法會在存在 @Adaptive
註解的實現類時爲 cachedAdaptiveClass
賦值。在這裏如果發現 cachedAdaptiveClass != null
則直接返回,否則進入動態創建自適應擴展類方法 createAdaptiveExtensionClass
。
這裏纔是我們理解 Dubbo SPI
關鍵,大家要耐心、仔細往下看。
private Class<?> createAdaptiveExtensionClass() {
//動態生成自適應擴展類代碼
String code = createAdaptiveExtensionClassCode();
//獲得類加載器
ClassLoader classLoader = findClassLoader();
//獲得 Compiler 自適應擴展類
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//編譯代碼生成 class
return compiler.compile(code, classLoader);
}
這個方法內大概內容就是動態生成自適應擴展類代碼,使用 Compiler
編譯該代碼。
我們主要看的在 createAdaptiveExtensionClassCode
方法內,先多嘴一句,動態生成自適應擴展類代碼需要保證接口內具有 @Adaptive
註解的方法
由於該方法內容較長,我決定一段一段分析,大家可以對照源碼閱讀。在下面文章中,如果存在 /* */
註釋則表示省略了一段代碼,註釋內容爲省略代碼的主要功能,大家可以注意一下。
Method[] methods = type.getMethods();
//判斷方法是否具有 @Adaptive 註解
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!");
這部分主要是判斷接口內是否存在 @Adaptive
註解的方法,如果不存在,那麼直接拋異常。
//生成包名
codeBuidler.append("package " + type.getPackage().getName() + ";");
//導入ExtensionLoader
codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
//生成類名(比如: Protocol$Adpative) 並且 實現接口 該接口
codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " 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);
//如果當前方法沒有 @Adaptive 註解,則在實現類的該方法體中拋出異常
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
//其它操作
}
這段代碼主要是判斷當前接口方法是否存在 @Adaptive
註解,如果不存在,則在實現方法中添加拋異常代碼
for (Method method : methods) {
/* 參數獲取 */
if (adaptiveAnnotation == null) {
/*不存在 @Adaptive 註解*/
} else {
int urlTypeIndex = -1;
//判斷該方法的參數中是否存在 URL 類型的 有的話進行標記
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 {
//...
}
//...
}
// 其它
}
這裏主要是檢測方法的參數中是否具有 URL
類型的參數。
繼續看參數沒有 URL
類型的時候處理方式
for (Method method : methods) {
/* 參數獲取 */
if (adaptiveAnnotation == null) {
/*不存在 @Adaptive 註解*/
} else {
/*獲得 URL 參數位置*/
if (urlTypeIndex != -1) {
/* 參數有URL*/
}
// 參數沒有URL類型
else {
String attribMethod = null;
// 遍歷所有參數 從參數類型的所有方法中 查看 get* 方法並且返回類型爲 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) {
//記錄 URL 參數的位置
urlTypeIndex = i;
//記錄返回 URL 類型的方法名
attribMethod = name;
break LBL_PTS;
}
}
}
//如果爲空 則表示所有方法的參數類型中不具有 URL 參數
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());
}
// 空指針檢測 生成的代碼如:if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
//url參數判空,生成代碼如下:if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);
//獲得url參數,生成代碼如下:com.alibaba.dubbo.common.URL url = arg0.getUrl();
s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
//其它
}
// 其它
}
這部分代碼主要就是從接口的方法中遍歷所有參數,查看參數是否具有 URL
屬性。至於爲什麼 Dubbo
一直在查找URL
參數,是因爲我們的所有信息在 Dubbo
中都是以 URL
爲總線的,通過 URL
可以獲得我們需要的信息。
繼續往下分析
for (Method method : methods) {
/* 參數獲取 */
if (adaptiveAnnotation == null) {
/*不存在 @Adaptive 註解*/
} else {
/* 獲得 URL 參數位置*/
if (urlTypeIndex != -1) {
/* 參數有URL*/
}
// 參數沒有URL類型
else {
/*從方法參數類型的方法中查找URL*/
}
//獲得方法上具有 @Adaptive 註解的值
String[] value = adaptiveAnnotation.value();
// 沒有設置value,則使用“擴展點接口名的點分隔 作爲value
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()};
}
//其它
}
// 其它
}
這部分也很簡單,從方法註解上獲得註解的值,可能有多個。如果不存在的話,根據接口名獲得,遇見大寫改爲小寫如果不是第一個符號還需要加 .
符號。
for (Method method : methods) {
/* 參數獲取 */
if (adaptiveAnnotation == null) {
/* 不存在 @Adaptive 註解 */
} else {
//獲得 URL 參數位置
if (urlTypeIndex != -1) {
/* 參數有URL */
}
// 參數沒有URL類型
else {
/*從方法參數類型的方法中查找URL*/
}
/*獲得 @Adaptive 註解的值*/
boolean hasInvocation = false;
//判斷是否具有 Invocation 類型的參數
for (int i = 0; i < pts.length; ++i) {
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// 空指針檢測 生成的代碼如:if (arg0 == null) throw new IllegalArgumentException("invocation == null");"
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
//獲得方法名 生成的代碼如:String methodName = arg0.getMethodName();
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
hasInvocation = true;
break;
}
}
//cachedDefaultName 還記得上面我們會加載 SPI 資源文件時會設置該值嗎?忘了可以回去看看 該值是從接口類的 @Adaptive 註解中獲得的
String defaultExtName = cachedDefaultName;
//tip:這段代碼通過遞歸方式生成 仔細觀察下面的 for 循環語句
String getNameCode = null;
// 由於在方法上 @Adaptive 註解的值可能有多個,從後往前遍歷 以最左爲準,主要是爲了兼容老版本擴展名參數。越[老]的擴展名參數越靠右,會被新的覆蓋。默認擴展名爲defaultExtName
for (int i = value.length - 1; i >= 0; --i) {
if(i == value.length - 1) {
//如果默認擴展名不爲空
if(null != defaultExtName) {
//至於爲什麼特判 protocol,是因爲 url 可以直接獲得
if(!"protocol".equals(value[i]))
if (hasInvocation)
//存在 Invocation 參數 生成的代碼如: url.getMethodParameter(methodName, "loadbalance", "random")
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
//生成代碼如:url.getParameter("proxy", "javassist")
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
//生成代碼如:( url.getProtocol() == null ? "dubbo" : url.getProtocol() )
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);
//其它
}
// 其它
}
代碼有些複雜,建議大家 debug
查看。主要是爲了獲得 extName
的參數。
for (Method method : methods) {
/* 參數獲取 */
if (adaptiveAnnotation == null) {
/* 不存在 @Adaptive 註解 */
} else {
//獲得 URL 參數位置
if (urlTypeIndex != -1) {
/* 參數有URL */
}
// 參數沒有URL類型
else {
/*從方法參數類型的方法中查找URL*/
}
/*獲得 @Adaptive 註解的值*/
/* 生成 extName 的代碼*/
//根據 extName 從 SPI 中獲得實例,具體代碼如:com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);
// 如果非 void 返回類型則生成返回值語句
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}
//下面就是生成返回值,內容如: return extension.export(arg0);
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(");");
}
// 其它
}
這部分代碼主要是爲了生成返回值。
繼續看最後一部分了
// public + 返回值全限定名 + 方法名 + (
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}");
大致就到這裏了,動態生成自適應擴展類大概就這麼多,簡單舉個例子。
大家可以看com.alibaba.dubbo.rpc.Protocol
接口,該 接口的實現類中不存在 @Adaptive
註解的實現類,所以在使用
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
時會動態生成,還記得我們的生成的原則吧:
- 判斷方法是否存在
@Adaptive
註解
– 不存在:自適應類的實現方法中拋異常
– 存在:判斷方法參數是否存在URL
類的參數
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
通過查看 Protocol
接口我們發現,該類的默認 SPI
擴展名爲 dubbo
,getDefaultPort
和 destroy
方法無 @Adaptive
註解。export 和 refer 具有 @Adaptive
註解。自動生成的Protocol
自適應類如下
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
//無 @Adaptive 接口 直接拋異常
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
//無 @Adaptive 接口 直接拋異常
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
然後就要回到 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().newInstance()
通過反射創建一個實例就不用說了,就剩下 injectExtension
方法了,這個方法中也就是 dubbo
的 IOC
了主要是爲了給該實例注入值
private T injectExtension(T instance) {
try {
//判斷objectFactory是否爲null 通過構造方法我們可以知道主要是判斷 type 是否等於ExtensionFactory.class
if (objectFactory != null) {
//遍歷所有方法查看是否具有 set 方法並且參數只有一個
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
//獲得變量名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
//對象工廠新建值 這邊可以繼續看我就不往下分析了
Object object = objectFactory.getExtension(pt, property);
//反射注入
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
寫了很多,需要大家慢慢消化。其實我這裏不可能把 SPI 的全部源碼分析的,該類中還有一些其它方法,不過核心都是在這片文章裏面。希望大家能夠由點到面,逐個擊破。
最後還想再說一句,有一點需要注意的是在 loadFile
時,如果發現一個類無 @Adaptive
註解並且該類中具有接口類的構造方法,那麼則任務該類爲包裝類,則向 cachedWrapperClasses
變量中緩存可以包裝的類。
比如 ProtocolFilterWrapper
和 ProtocolListenerWrapper
均可以包裝 Protocol
的實現類。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
public ProtocolListenerWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
具體的包裝實現是在ExtensionLoader
類的createExtension
方法中
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, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//判斷是否存在包裝類
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
//對實例進行包裝
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//返回包裝後的實例
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}