概述
ExtensionLoader是dubbo項目的核心組件,正是有了它,使得dubbo的可擴展性得到了極大的提升.dubbo設計之精良,從這點可見一斑.
dubbo的擴展機制,類似于jdk所提供的spi機制. 但是dubbo的更加強大,體現在:
- jdk的spi默認會加載并實例化所有擴展點類,如果對應擴展點沒用到且實例化消耗大,則是個耗資源的點。
- 不支持依賴注入,即如果在擴展點類a中,有b擴展點屬性實例,則在加載擴展點類a時不會自動注入b。但是dubbo支持依賴注入(根據目標類的setter方法注入
- 加載擴展點失敗會吞掉異常,比如加載一個擴展類a,如果其所依賴的jar找不到,不會報這個錯誤,反而會報不支持。
首先,先手寫一個demo利用dubbo的擴展機制,實現一個自己的服務.初步體驗一下ExtensionLoader的用法.
首先定義一個(SPI)接口
//必須有 @spi注解 沒有就報錯
@SPI
public interface BaseExt {
@Adaptive
String echo(String str, URL url);
}
然后定義2個實現類
@Adaptive
public class ExtOne implements BaseExt {
@Override
public String echo(String str, URL url) {
System.out.println("one ... " + str);
return null;
}
}
public class ExtTwo implements BaseExt {
@Override
public String echo(String str, URL url) {
System.out.println("two ... " + str);
return null;
}
}
最后要在 classpath(src/main/resources)下創(chuàng)建一個META-INF/dubbo/internal的目錄,創(chuàng)建一個名字為spi接口全路徑(com.xx.BaseExt)的文件里面寫入一下內容(key=value)
one=com.xx.ExtOne
two=com.xx.ExtTwo
搞定,最后寫一個測試類
public class Main {
public static void main(String[] args) {
//
ExtensionLoader<BaseExt> loader = ExtensionLoader.getExtensionLoader(BaseExt.class);
BaseExt adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test");
adaptiveExtension.echo("d", url);
}
得到以下輸出
one ... d
入門非常簡單,例子更簡單.從上面能得到一下總結: 用法其實和jdk的ServiceLoader 一個思想, (接口+實現類+配置文件) . 其實dubbo的擴展一直要遵守一下的約定
- Dubbo約定,需要通過SPI加載的接口,在接口上必須添加注解@SPI。
- Dubbo自己實現了加載器(ExtensionLoader)而非使用JDK自帶的(ServiceLoader)。
- 實現類的配置文件名稱還是以接口全路徑命名約定不變,但是內容以key=value格式存在,key表示實現類的別名,value表示實現類全路徑(JDK自帶的SPI約定內容是實現類全路徑)。
- ExtensionLoader分別從依賴的JAR包中的,META-INF/dubbo/internal/目錄、META-INF/dubbo/目錄、META-INF/services/目錄,這個3個目錄中掃描尋找實現類配置文件。
- 所有SPI接口,都會有一個要求有一個適配類(即在實現類打上@Adaptive注解就被認定為適配類,如果掃描后所有實現類都沒有該注解,系統(tǒng)會生成一個)。在系統(tǒng)里面使用時,一般都先獲取接口的適配類實例,然后再由入參配置來決定選擇哪個具體實現類調用。實現了可配置動態(tài)切換實現類。
- 在實現類里面,可以通過提供setter方法來達到依賴注入。
其實dubbo一個spi服務一般會有多種實現 ,具體使用哪一個實現呢? 這個是憂優(yōu)先級的 .
- 首先,在實現類(方法上無效)上面加
@Adaptive注解,優(yōu)先級會最高 - 如果所有實現類都沒有這個注解(接口方法上的不算),回去尋找url路徑中的指明的那個注解 ,比如上面mian函數中的 ,spi接口的分段形式(XxxYyy類的分段形式 : xxxx.yyy)作為key,配置文件中的key作為value, 根據這個value也可以找到具體實現類,比如上面如果URL中這樣寫:
URL url = URL.valueOf("test://localhost/test?base.ext=one");
就表示 spi接口BaseExt 要求使用OneExt作為實現類 - 如果URL中也沒有指定 ,還有次次優(yōu)先級的方式: spi注解中加入默認值
比如 BaseExt的spi注解中這樣寫:@SPI("two")此時,TwoExt 就作為默認實現類提供服務了 .注意這個優(yōu)先級哦
知道了上面的用法和各種玩法. 下面是時候再探究竟了 .我們看下源代碼吧 .
ExtensionLoader<BaseExt> loader = ExtensionLoader.getExtensionLoader(BaseExt.class);
引出
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
//說明SPI獲取的必須是接口類型
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
// 說明SPI服務的接口必須有`@SPI`這個注解
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//第一次獲取必定拿到null
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//new一個擴展器處理這個type,new操作的特別之處在于會創(chuàng)建一個factory來獲取目標type,從而得到擴展工廠,
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
創(chuàng)建擴展工廠需要ExtensionFactory
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
從上面可以看到 ,拿到 ExtensionFactory之后,會放入 ExtensionLoader的內部類變量
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();里面 .這個玩意就是緩存著了. 所有獲取過的擴展對象都會在這里存下來. 后面再拿就走這個緩存了. 我們再來看main中下一句引子BaseExt adaptiveExtension = loader.getAdaptiveExtension();
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//首次拿擴展實例會需要先創(chuàng)建 ,
//創(chuàng)建好了 才回緩存這個實例
//下面這句是核心邏輯 ,
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;
}
調用創(chuàng)建擴展類函數 ExtensionLoader.createAdaptiveExtension
private T createAdaptiveExtension() {
try {
//創(chuàng)建擴展執(zhí)行依賴插入檢查和依賴插入
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}
繼續(xù)看獲取的邏輯 getAdaptiveExtensionClass
// 開始獲取擴展
private Class<?> getAdaptiveExtensionClass() {
//這里如果獲取下面步驟
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
//下面先沿著 getExtensionClasses 這條線 ,嘗試獲取擴展實現類
//下面是獲取擴展的
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;
}
// 這個方法就是 核心邏輯了 ,會從文件中掃描指定接口的實現類
// 此方法已經getExtensionClasses方法同步過。
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<?>>();
//從 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;
}
//讀取邏輯不在貼代碼了 ,就是獲取文件 按照key=value的配置文件的形式解析服務配置文件
//但是有一段代碼貼下 ,就是在解析的過程中,發(fā)現實現類頭上有 @Adaptive注解的 ,就把他設置到當前的 cachedAdaptiveClass 屬性中去.(摘自方法 loadFile)
//
if (clazz.isAnnotationPresent(Adaptive.class)) {
if(cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (! cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
}
// ..................
到此為止,這條線已經走完,完成之后(看下面),如果在load的過程中 找到了自動適配的類(@adaptive注解的實現類) ,則直接返回
cachedAdaptiveClass完成任務 .否則開始走另一條線createAdaptiveExtensionClass
// 這塊代碼是上面開始的時候代碼,
private Class<?> getAdaptiveExtensionClass() {
//這里如果獲取下面步驟
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
接下來看看另一條線吧.
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);
}
//不全部粘貼了 ,詳細的自己看源碼了 ,篇幅有限
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();
//.......................省略 ,
可以看出,上面就是要通過代碼創(chuàng)建一個java文件,并用compile進行編譯
下面是一個通過編譯得到的例子 (可讀性比較差,但是能看出大概意思),
package com.xxx.dubbo;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class BaseExt$Adpative implements com.zuosh.dts2018.dubbo.BaseExt {
public java.lang.String echo(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("base.ext");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.xxx.dubbo.BaseExt) name from url(" + url.toString() + ") use keys([base.ext])");
com.xxx.dubbo.BaseExt extension = (com.xxx.dubbo.BaseExt)ExtensionLoader.getExtensionLoader(com.xxx.dubbo.BaseExt.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
}
上面就是獲取自適應擴展實現類的全部過程. 我們從一個方法逐漸深入,看了擴展器的其中一個功能 (獲取自適應擴展類) ,其他各種五花八門的獲取實現類的方式(如下圖)都是差不多同樣的道理.都值得我們一一學習和參考.
image.png
后面的其他擴展原理都是大同小異,但是重載的很多方法給我們日常編碼提供了很好的例子,讓我們知道怎么樣寫一個好的擴展,而不是僅僅實現功能.
