相信對dubbo有過了解的小伙伴應該知道,dubbo之所以被廣泛的使用义图,其中最重要的一個原因是因為其優(yōu)秀的可擴展性。而如此良好的擴展性有兩個密不可分的原因电抚,一個是設計模式,另一個就是dubbo自身獨特的擴展點機制-dubbo SPI街望,本文將主要從以下幾個方面來詳細解讀dubbo SPI的實現機制。
- java spi
- dubbo 中spi優(yōu)化與特性
- 源碼解讀擴展點注解
- 總結
一、java spi
在講解Dubbo SPI之前赫舒,先了解一下Java SPI是怎么使用的有咨。SPI的全稱是Service Provider Interface
,起初是提供給廠商做插件開發(fā)的滤祖。通俗點解釋其實就是策略模式挤牛,定義一個接口,有多個實現,只不過接口對應的實現不在代碼中直接聲明喜德,而是通過配置文件來配置這個對應實現的關系观蓄。具體步驟如下:
(1) 定義一個接口及對應的方法。 (2) 編寫該接口的一個實現類辑舷。
(3) 在META-INF/services/目錄下,創(chuàng)建一個以接口全路徑命名的文件玖绿,如com.test.spi.PrintService
(4) 文件內容為具體實現類的全路徑名寂嘉,如果有多個父叙,則用分行符分隔躬贡。
(5) 在代碼中通過java.util.ServiceLoader來加載具體的實現類。
Java SPI示例代碼
public interface Printservice ( <——① SPI 接口定義
void printlnfo();
}
public class PrintServicelmpl implements Printservice { _②SPI接口實現類
Override
public void printlnfo() {
System.out.println("hello world");
}
}
public static void main(String[] args) ( //調用SPI具體的實現
ServiceLoader<PrintService> serviceServiceLoader =
ServiceLoader.load(PrintService.class);
for (Printservice printservice : serviceServiceLoader) ( <-------------
//此處會輸出:hello world 獲取所有的SPI實現,循環(huán)調用
printService.printInfo();
}
}
dubbo官方文檔中對于java spi的缺點給出了一下兩點
- JDK 標準的 SPI 會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時潦匈,但如果沒用上也加載,會很浪費資源卧斟。
- 如果擴展點加載失敗留特,連擴展點的名稱都拿不到了菱鸥。比如:JDK 標準的 ScriptEngine艾凯,通過 getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在贝乎,導致 RubyScriptEngine 類加載失敗情连,這個失敗原因被吃掉了,和 ruby 對應不起來览效,當用戶執(zhí)行 ruby 腳本時却舀,會報不支持 ruby,而不是真正失敗的原因朽肥。
二禁筏、dubbo spi
針對以上兩點,我們來看dubbo做了哪些優(yōu)化:
2.1 按需獲取擴展點實現
- 在上文java spi的演示代碼中我們看到衡招,java.util.ServiceLoader會一次把Printservice接口下的所有實現類全部初始化。用戶直接調用即可每强。Dubbo SPI只是加載配置文件中的類始腾, 并分成不同的種類緩存在內存中,而不會立即全部初始化空执,在性能上有更好的表現浪箭。具體的實現原理會在后面講解,此處演示一個使用示例辨绊。
PrintService接口的Dubbo SPI改造
PrintService接口的Dubbo SPI改造
① 在目錄META-INF/dubbo/internal下建立配置文com.test.spi.Printservice,文件內容如下
impl=com.test.spi.PrintServiceImpl
② 為接口類添加SPI注解奶栖,設置默認實現為impl
@SPI("impl")
public interface Printservice {
void printlnfo();
)
③實現類不變
public class PrintServicelmpl implements Printservice (
Override
public void printlnfo() (
System.out println("hello world");
}
}
④調用Dubbo SPI
public static void main(String[] args) (
//通過 ExtensionLoader 獲取接口PrintService.class 的默認實現
PrintService printservice = ExtensionLoader
.getExtensionLoader(PrintService.class).getDefaultExtension();
//此處會輸出 PrintServicelmpl 打印的 hello world
printService.printInfo();
}
我們發(fā)現,在dubbo中门坷,如果一個擴展點有多個實現宣鄙,我們可以不畢直接加載所有實現,而是根據自己的需要獲取擴展點實現默蚌,具體實現原理下文我們通過源碼分析冻晤。
2.2、異常處理
Java SPI加載失敗绸吸,可能會因為各種原因導致異常信息被“吞掉”鼻弧,導致開發(fā)人員問題追蹤比較困難设江。Dubbo SPI在擴展加載失敗的時候會先拋出真實異常并打印日志。擴展點在被動加載的時候攘轩,即使有部分擴展加載失敗也不會影響其他擴展點和整個框架的使用
2.3叉存、IOC和AOP機制
- 在dubbo中,很多功能都是通過擴展點來實現的度帮,既然如此歼捏,一旦擴展點很多的話,擴展點之間的依賴關系怎么處理也是一個問題够傍,而在dubbo中則是通過自動裝配甫菠,即如果實例化一個擴展點實現,會繼續(xù)看有沒有依賴此擴展點的擴展點冕屯。判斷方法也很簡單寂诱,就是通過set方法,即一個擴展點可以通過setter方法直接注入其他擴展點安聘。這個和spring的IOC原理類似痰洒,所以我們稱它為dubbo spi中的IOC,也稱為擴展點的自動擴展特性浴韭,具體實現邏輯方法為
injectExtension(T instance)
丘喻,后面會詳細解釋。這么說有點抽象念颈,我們來看官網中給出的具體示例:
有兩個為擴展點 CarMaker(造車者)泉粉、WheelMaker (造輪者)
public interface CarMaker {
Car makeCar();
}
public interface WheelMaker {
Wheel makeWheel();
}
CarMaker 的一個實現類
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;
public void setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}
public Car makeCar() {
// ...
Wheel wheel = wheelMaker.makeWheel();
// ...
return new RaceCar(wheel, ...);
}
}
ExtensionLoader
加載 CarMaker
的擴展點實現 RaceCarMaker
時,setWheelMaker
方法的 WheelMaker
也是擴展點則會注入 WheelMaker
的實現榴芳。
這里帶來另一個問題嗡靡,ExtensionLoader
要注入依賴擴展點時,如何決定要注入依賴擴展點的哪個實現窟感。在這個示例中讨彼,即是在多個WheelMaker
的實現中要注入哪個。
這個問題在下面一點 [擴展點自適應]特性的時候講解
- 熟悉設計模式的應該知道柿祈,裝飾者模式是很常用的一種設計模式哈误。它通常用來在不改變原有對象的行為方法的同時,用來對原有對象進行方法增強躏嚎。而在dubbo中蜜自,實例化一個擴展點實現的同時,也會判斷此對象有沒有作為一個包裝類對象中構造方法的對象紧索,如果是袁辈,也會實例化該
wrapper
包裝類。舉例說明:
private T createExtension(String name){
//這里省略了部分代碼
.......
/**
* 向擴展類注入其依賴的擴展點屬性珠漂,這里是體現了擴展點自動裝配的特性
*/
injectExtension(instance);
/**
* 這里的cachedWrapperClasses對象 在執(zhí)行getExtensionClasses方法時已經賦值
* 擴展點自動包裝特性晚缩,ExtensionLoader在加載擴展時尾膊,如果發(fā)現這個擴展類包含其他擴展點作為構造函數的參數,
* 則這個擴展類會被認為是wrapper類荞彼,比如 ProtocolFilterWrapper,就是在構造函數中注入了 Protocol類型的擴展點
* 那么這個wrapper類也會被實例化并且注入擴展點屬性
*/
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//wrapper對象的實例化(injectExtension():向擴展類注入其依賴的屬性,如擴展類A又依賴了擴展類B冈敛,那么就向A中注入擴展類B)
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
.......
}
在上面這個創(chuàng)建擴展點的方法中,擴展點自動裝配以后鸣皂,會繼續(xù)對包裝類對象進行注入抓谴。這個和aop原理一樣,稱為dubbo spi中的aop寞缝,也叫擴展點的自動包裝癌压。
三、源碼解讀擴展點注解
上文大致說明了dubbo spi對于java spi的優(yōu)化點荆陆,下面我們針對優(yōu)化點進行一波源碼分析滩届。
3.1、@SPI 注解源碼解讀
首先看下按需獲取獲取擴展點實現被啼。這里涉及到一個注解 @SPI,在上文Dubbo SPI改造示例代碼中也有體現帜消,@SPI注解可以使用在類、接口和枚舉類上浓体,Dubbo框架中都是使用在接口上泡挺。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* default extension name
*/
String value() default "";
}
我們可以看到SPI注解有一個value屬性,通過這個屬性命浴,我們可以傳入不同的參數來設置這個接口的默認實現類娄猫。例如,我們可以看到Transporter接口使用Netty作為默認實現
@SPI(”netty”)
public interface Transporter!
}
下面具體看一下是怎么根據名稱獲取到具體的實現類的生闲,代碼入口:
org.apache.dubbo.common.extension.ExtensionLoader#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();
//從緩存中獲取稚新,緩存中沒有梧喷,則創(chuàng)建障簿,這里使用了雙重檢查鎖
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//創(chuàng)建擴展點實現類
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
/**
* 1闸婴、加載定義文件中的各個子類,然后將目標name對應的子類返回后進行實例化冲茸。
* 2、通過目標子類的set方法為其注入其所依賴的bean缅帘,這里既可以通過SPI轴术,也可以通過Spring的BeanFactory獲取所依賴的bean,injectExtension(instance)钦无。
* 3逗栽、獲取定義文件中定義的wrapper對象,然后使用該wrapper對象封裝目標對象失暂,并且還會調用其set方法為wrapper對象注入其所依賴的屬性
* @param name
* @return
*/
@SuppressWarnings("unchecked")
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);
/**
* 這里的cachedWrapperClasses對象 在執(zhí)行getExtensionClasses方法時已經賦值
* 擴展點自動包裝特性鳄虱,ExtensionLoader在加載擴展時,如果發(fā)現這個擴展類包含其他擴展點作為構造函數的參數凭峡,
* 則這個擴展類會被認為是wrapper類拙已,比如 ProtocolFilterWrapper,就是在構造函數中注入了 Protocol類型的擴展點
* 那么這個wrapper類也會被實例化并且注入擴展點屬性
*/
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//wrapper對象的實例化(injectExtension():向擴展類注入其依賴的屬性,如擴展類A又依賴了擴展類B,那么就向A中注入擴展類B)
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);
}
}
createExtension是個關鍵的方法:此方法基本分為三個步驟:
1摧冀、加載定義文件中的各個子類倍踪,然后將目標name對應的子類返回后進行實例化。
2索昂、通過目標子類的set方法為其注入其所依賴的bean建车,這里既可以通過SPI,也可以通過Spring的BeanFactory獲取所依賴的bean椒惨,injectExtension(instance)缤至。
3、獲取定義文件中定義的wrapper對象框产,然后使用該wrapper對象封裝目標對象凄杯,并且還會調用其set方法為wrapper對象注入其所依賴的屬性
1、getExtensionClasses 方法比較長秉宿,在此不一一列舉戒突,主邏輯就是獲取到擴展點的所有實現類,中間會加入各種緩存提高性能描睦,需要注意的是這里獲取的只是擴展點的實現類膊存,并沒有實例化,這也印證了我們上面所說的按照需要獲取擴展實現類忱叭,并且只是加載配置文件中的類隔崎, 并分成不同的種類緩存在內存中,而不會立即全部初始化韵丑,在性能上有更好的表現爵卒。
2、injectExtension 是擴展點自動擴展的特性具體實現撵彻,基本原理:
方法總體實現了類似Spring的IoC機制钓株,其實現原理比較簡單:首先通
過反射獲取類的所有方法,然后遍歷以字符串set開頭的方法陌僵,得到set方法的參數類型轴合,再通過ExtensionFactory尋找參數類型相同的擴展類實例,如果找到碗短,就設值進去
/**
* 注入擴展類
* 像擴展類中注入其依賴的屬性受葛,如擴展類A又依賴了擴展類B,那么就向A中注入擴展類B
*
*injectExtension方法總體實現了類似Spring的IoC機制,其實現原理比較簡單:首先通
* 過反射獲取類的所有方法总滩,然后遍歷以字符串set開頭的方法纲堵,得到set方法的參數類型,再通
* 過ExtensionFactory尋找參數類型相同的擴展類實例咳秉,如果找到婉支,就設值進去
* @param instance
* @return
*/
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//執(zhí)行set方法
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;
}
3.2、擴展點自適應注解:?Adaptive
上文中我們遺留了一個問題澜建,ExtensionLoader 要注入依賴擴展點時向挖,如何決定要注入依賴擴展點的哪個實現。這就需要提到擴展點的自適應注解炕舵,?Adaptive何之。
@Adaptive注解可以標記在類、接口咽筋、枚舉類和方法上溶推,但是在整個Dubbo框架中,只有幾個地方使用在類級別上奸攻,如AdaptiveExtensionFactory和AdaptiveCompiler,其余都標注在方法上蒜危。如果標注在接口的方法上,即方法級別注解睹耐,則可以通過參數動態(tài)獲得實現類辐赞,方法級別注解在第一次getExtension時,會自動生成和編譯一個動態(tài)的Adaptive類硝训,從而達到動態(tài)實現類的效果响委。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
//數組,可以設置多個key窖梁,會按順序依次匹配
String[] value() default {};
}
該注解也可以傳入value參數赘风,是一個數組。我們在代碼清單4.9中可以看到纵刘,Adaptive可以傳入多個key值邀窃,在初始化Adaptive注解的接口時,會先對傳入的URL進行key值匹配假哎,第一個key沒匹配上則匹配第二個蛔翅,以此類推。直到所有的key匹配完畢位谋,如果還沒有匹配到, 則會使用“駝峰規(guī)則”匹配堰燎,如果也沒匹配到掏父,則會拋出IllegalStateException異常。 什么是"駝峰規(guī)則”呢秆剪?如果包裝類(Wrapper 沒有用Adaptive指定key值赊淑,則Dubbo會自動把接口名稱根據駝峰大小寫分開爵政,并用符號連接起來,以此來作為默認實現類的名稱陶缺,如下面示例中的 SimpleExt 會被轉化為simple.ext钾挟。
對于@Adaptive 注解的解析可以從這個單元測試著手:
org.apache.dubbo.common.extension.ExtensionLoader_Adaptive_Test#test_getAdaptiveExtension_defaultAdaptiveKey
@SPI("impl1")
public interface SimpleExt {
// @Adaptive example, do not specify a explicit key.
@Adaptive
String echo(URL url, String s);
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
// no @Adaptive
String bang(URL url, int i);
}
public class SimpleExtImpl1 implements SimpleExt {
public String echo(URL url, String s) {
return "Ext1Impl1-echo";
}
public String yell(URL url, String s) {
return "Ext1Impl1-yell";
}
public String bang(URL url, int i) {
return "bang1";
}
}
public class SimpleExtImpl2 implements SimpleExt {
public String echo(URL url, String s) {
return "Ext1Impl2-echo";
}
public String yell(URL url, String s) {
return "Ext1Impl2-yell";
}
public String bang(URL url, int i) {
return "bang2";
}
}
public class SimpleExtImpl3 implements SimpleExt {
public String echo(URL url, String s) {
return "Ext1Impl3-echo";
}
public String yell(URL url, String s) {
return "Ext1Impl3-yell";
}
public String bang(URL url, int i) {
return "bang3";
}
}
@Test
public void test_getAdaptiveExtension_defaultAdaptiveKey() throws Exception {
{
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
Map<String, String> map = new HashMap<String, String>();
URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
String echo = ext.echo(url, "haha");
assertEquals("Ext1Impl1-echo", echo);
}
{
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
Map<String, String> map = new HashMap<String, String>();
map.put("simple.ext", "impl2");
URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
String echo = ext.echo(url, "haha");
assertEquals("Ext1Impl2-echo", echo);
}
}
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//創(chuàng)建自適應擴展點實例
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
private Class<?> getAdaptiveExtensionClass() {
//加載所有擴展點實現類
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//此處是關鍵的一步,生成自適應擴展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
createAdaptiveExtensionClass
是關鍵的一步:
為擴展點接口自動生成實現類字符串饱岸,實現類主要包含以下邏輯:為接口中每個有Adaptive注解的方法生成默認實現(沒有注解的方法則生成空實現)掺出,每個默認實現都會從URL中提取Adaptive參數值,并以此為依據動態(tài)加載擴展點苫费。然后汤锨,框架會使用不同的編譯器,把實現類字符串編譯為自適應類并返回
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
}
通過debug我們可以看到生成的類字符串到底長什么樣百框,這里我們貼出來看一下:
* package org.apache.dubbo.common.extension.support;
* package org.apache.dubbo.common.extension.ext1;
* import org.apache.dubbo.common.extension.ExtensionLoader;
* public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt {
* public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) {
* throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!");
* }
* public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
* if (arg0 == null) throw new IllegalArgumentException("url == null");
* org.apache.dubbo.common.URL url = arg0;
* String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
* if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])");
* org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
* return extension.yell(arg0, arg1);
* }
* public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
* if (arg0 == null) throw new IllegalArgumentException("url == null");
* org.apache.dubbo.common.URL url = arg0;
* String extName = url.getParameter("simple.ext", "impl1");
* if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
* org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
* return extension.echo(arg0, arg1);
* }
* }
由上面動態(tài)生成的$Adaptive類可以得知闲礼,每個默認實現都會從URL中提取Adaptive參數值,并以此為依據動態(tài)加載擴展點铐维,如示例所示柬泽,就是根據url中獲取到extName參數,然后調用 getExtension(extName)
嫁蛇。
如果一個接口上既有@SPI(”impl2”)注解锨并,方法上又有@Adaptive(”impl1”)注解,那么會以哪個key作為默認實現呢棠众?由上面動態(tài)生成的Adaptive類可以得知琳疏,最終動態(tài)生成的實現方法會是url.getParameter(simple.ext, "impl”),即優(yōu)先通過?Adaptive注解傳入的key去查找擴展實現類;如果沒找到闸拿,則通過@SPI注解中的key去查找空盼;如果@SPI注解中沒有默認值,則把類名轉化為key,再去查找新荤。
除了上面的這個示例揽趾,還有一個示例可以參考,我們看dubbo中的傳輸協議苛骨,接口默認為dubbo的傳輸協議篱瞎,在服務暴露和引用的方法中,加了@Adaptive注解痒芝。
@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;
}
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
以上獲取 protocol的代碼俐筋,會生成一個中間代理類如下:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
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 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.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);
}
}
和上述例子相同,在動態(tài)獲取協議的過程中严衬,也是通過url中的參數來動態(tài)獲取澄者,如果url中protocol參數為空,則默認使用dubbo協議,如果不為空粱挡,比如服務暴露的過程中赠幕,先本地暴露,那么url中protocol = injvm询筏,那么獲取到的協議就是InjvmProtocol榕堰。
3.3、擴展點自動激活注解:?Active
上面我們提到自適應的擴展實現機制動態(tài)尋找實現類的方式比較靈活嫌套,但只能激活一個具體的實現類逆屡,如果需要多個實現類同時被激活,如Filter可以同時有多個過濾器;或者根據不同的條件灌危,同時激活多個實現類康二, 如何實現?這就涉及最后一個特性一一自動激活
使用@Activate注解勇蝙,可以標記對應的擴展點默認被激活啟用沫勿。該注解還可以通過傳入不同的參數,設置擴展點在不同的條件下被自動激活味混。主要的使用場景是某個擴展點的多個實現類需要同時啟用(比如Filter擴展點)
@Active 可以傳入的參數很多
org.apache.dubbo.common.extension.ExtensionLoaderTest#testLoadActivateExtension
測試用來來進行解析产雹。
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
//獲取擴展點所有實現類
getExtensionClasses();
//遍歷整個@Acitve注解集合
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
//group條件匹配
if (isMatchGroup(group, activateGroup)) {
T ext = getExtension(name);
if (!names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
exts.add(ext);
}
}
}
//排序
exts.sort(ActivateComparator.COMPARATOR);
}
List<T> usrs = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
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;
}
該方法主線流程分為4步:
(1) 依舊是獲取擴展點的所有實現
(2) 遍歷整個?Activate注解集合,根據傳入URL匹配條件(匹配group> name等)翁锡,得
到所有符合激活條件的擴展類實現蔓挖。然后根據@Active中配置的before、after馆衔、order等參數進行排序
(3) 遍歷所有用戶自定義擴展類名稱瘟判,根據用戶URL配置的順序,調整擴展點激活順序
(遵循用戶在 URL 中配置的順序角溃,例如 URL 為 test ://localhost/test?ext=orderlJdefault,則擴展點ext的激活順序會遵循先order再default,其中default代表所有有@Activate注解的擴展點)拷获。
(4) 返回所有自動激活類集合。
四减细、總結
好了匆瓜,擼了這么久源碼,總結一波:
dubbo Spi為了優(yōu)化java spi的缺點未蝌,引入了可以按照需要獲取擴展點的實現類驮吱。其中
@SPI
注解通常用在接口上,指定擴展點的實現類萧吠。并且在加載擴展點的過程中左冬,引入了自動擴展和自動封裝擴展點的特性,在自動擴展的過程過程中纸型,因為需要確定自動擴展的是具體哪一個實現類又碌,
又引入了@Adaptive
注解九昧,該注解大部分用在方法級別,通過生成動態(tài)的$Adaptive類來解析參數毕匀,并通過參數動態(tài)獲取具體的實現類。但是其自適應擴展點實現只能一個癌别,
又引入了@Active
注解皂岔,此注解可以通過傳入不同的參數,設置擴展點在不同的條件下多個實現類被自動激活展姐,主要的使用場景是某個擴展點的多個實現類需要同時啟用(比如Filter擴展點)躁垛。
備注:文中示例代碼版本:2.7.3
參考文獻:
1、dubbo官網
2圾笨、書籍:深入理解Apache Dubbo與實戰(zhàn)