前言
我們?cè)谕谖恼轮校?jīng)深入分析過(guò)Java的SPI機(jī)制十嘿,它是一種服務(wù)發(fā)現(xiàn)機(jī)制因惭。具體詳見(jiàn):深入理解JDK的SPI機(jī)制
在繼續(xù)深入Dubbo之前,我們必須先要明白Dubbo中的SPI機(jī)制绩衷。因?yàn)橛形淮笊瘢ㄘ┰@樣說(shuō)過(guò):
要想理解Dubbo蹦魔,必須要先搞明白Dubbo SPI機(jī)制,不然會(huì)非常暈咳燕。
一勿决、背景
1、來(lái)源
Dubbo 的擴(kuò)展點(diǎn)加載從 JDK 標(biāo)準(zhǔn)的 SPI (Service Provider Interface) 擴(kuò)展點(diǎn)發(fā)現(xiàn)機(jī)制加強(qiáng)而來(lái)招盲。但還有所不同低缩,它改進(jìn)了JDK標(biāo)準(zhǔn)的 SPI的以下問(wèn)題:
JDK 標(biāo)準(zhǔn)的 SPI 會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn),如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí)宪肖,但如果沒(méi)用上也加載表制,會(huì)很浪費(fèi)資源健爬。
如果擴(kuò)展點(diǎn)加載失敗,連擴(kuò)展點(diǎn)的名稱都拿不到了么介。比如:JDK 標(biāo)準(zhǔn)的 ScriptEngine娜遵,通過(guò) getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因?yàn)樗蕾嚨?jruby.jar 不存在壤短,導(dǎo)致 RubyScriptEngine 類加載失敗设拟,這個(gè)失敗原因被吃掉了,和 ruby 對(duì)應(yīng)不起來(lái)久脯,當(dāng)用戶執(zhí)行 ruby 腳本時(shí)纳胧,會(huì)報(bào)不支持 ruby,而不是真正失敗的原因帘撰。
增加了對(duì)擴(kuò)展點(diǎn) IoC 和 AOP 的支持跑慕,一個(gè)擴(kuò)展點(diǎn)可以直接 setter 注入其它擴(kuò)展點(diǎn)。
2摧找、約定
在擴(kuò)展類的 jar 包內(nèi)核行,放置擴(kuò)展點(diǎn)配置文件 META-INF/dubbo/接口全限定名
,內(nèi)容為:配置名=擴(kuò)展實(shí)現(xiàn)類全限定名
蹬耘,多個(gè)實(shí)現(xiàn)類用換行符分隔。
3综苔、配置文件
Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下惩系,幾乎所有的功能都有擴(kuò)展點(diǎn)實(shí)現(xiàn)。
我們以Protocol接口為例如筛,它里面有很多實(shí)現(xiàn)堡牡。
二、Dubbo SPI
通過(guò)上圖我們可以看到妙黍,Dubbo SPI和JDK SPI配置的不同悴侵,在Dubbo SPI中可以通過(guò)鍵值對(duì)的方式進(jìn)行配置瞧剖,這樣就可以按需加載指定的實(shí)現(xiàn)類拭嫁。
Dubbo SPI的相關(guān)邏輯都被封裝到ExtensionLoader
類中,通過(guò)ExtensionLoader
我們可以加載指定的實(shí)現(xiàn)類抓于,一個(gè)擴(kuò)展接口就對(duì)應(yīng)一個(gè)ExtensionLoader
對(duì)象做粤,在這里我們把它親切的稱為:擴(kuò)展點(diǎn)加載器。
我們先看下它的屬性:
public class ExtensionLoader<T> {
//擴(kuò)展點(diǎn)配置文件的路徑捉撮,可以從3個(gè)地方加載到擴(kuò)展點(diǎn)配置文件
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
//擴(kuò)展點(diǎn)加載器的集合
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
//擴(kuò)展點(diǎn)實(shí)現(xiàn)的集合
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
//擴(kuò)展點(diǎn)名稱和實(shí)現(xiàn)的映射緩存
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
//拓展點(diǎn)實(shí)現(xiàn)類集合緩存
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
//擴(kuò)展點(diǎn)名稱和@Activate的映射緩存
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
//擴(kuò)展點(diǎn)實(shí)現(xiàn)的緩存
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
}
ExtensionLoader會(huì)把不同的擴(kuò)展點(diǎn)配置和實(shí)現(xiàn)都緩存起來(lái)怕品。同時(shí),Dubbo在官網(wǎng)上也給了我們提醒:擴(kuò)展點(diǎn)使用單一實(shí)例加載(請(qǐng)確保擴(kuò)展實(shí)現(xiàn)的線程安全性)巾遭,緩存在 ExtensionLoader
中肉康。下面我們看幾個(gè)重點(diǎn)方法闯估。
1、獲取擴(kuò)展點(diǎn)加載器
我們首先通過(guò)ExtensionLoader.getExtensionLoader()
方法獲取一個(gè) ExtensionLoader 實(shí)例吼和,它就是擴(kuò)展點(diǎn)加載器涨薪。然后再通過(guò) ExtensionLoader 的 getExtension 方法獲取拓展類對(duì)象。這其中炫乓,getExtensionLoader 方法用于從緩存中獲取與拓展類對(duì)應(yīng)的 ExtensionLoader刚夺,若緩存未命中,則創(chuàng)建一個(gè)新的實(shí)例末捣。
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;
}
比如你可以通過(guò)下面這樣侠姑,來(lái)獲取Protocol接口的ExtensionLoader實(shí)例:
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
就可以拿到擴(kuò)展點(diǎn)加載器的對(duì)象實(shí)例:
com.alibaba.dubbo.common.extension.ExtensionLoader[com.alibaba.dubbo.rpc.Protocol]
2、獲取擴(kuò)展類對(duì)象
上一步我們已經(jīng)拿到加載器箩做,然后可以根據(jù)加載器實(shí)例莽红,通過(guò)擴(kuò)展點(diǎn)的名稱獲取擴(kuò)展類對(duì)象。
public T getExtension(String name) {
//校驗(yàn)擴(kuò)展點(diǎn)名稱的合法性
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
// 獲取默認(rèn)的拓展實(shí)現(xiàn)類
if ("true".equals(name)) {
return getDefaultExtension();
}
//用于持有目標(biāo)對(duì)象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
它先嘗試從緩存中獲取邦邦,未命中則創(chuàng)建擴(kuò)展對(duì)象船老。那么它的創(chuàng)建過(guò)程是怎樣的呢?
private T createExtension(String name) {
//從配置文件中獲取所有的擴(kuò)展類圃酵,Map數(shù)據(jù)結(jié)構(gòu)
//然后根據(jù)名稱獲取對(duì)應(yīng)的擴(kuò)展類
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//通過(guò)反射創(chuàng)建實(shí)例柳畔,然后放入緩存
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//注入依賴
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 包裝為Wrapper實(shí)例
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);
}
}
這里的重點(diǎn)有兩個(gè),依賴注入和Wrapper包裝類郭赐,它們是Dubbo中IOC 與 AOP 的具體實(shí)現(xiàn)薪韩。
2.1、依賴注入
向拓展對(duì)象中注入依賴捌锭,它會(huì)獲取類的所有方法俘陷。判斷方法是否以 set 開(kāi)頭,且方法僅有一個(gè)參數(shù)观谦,且方法訪問(wèn)級(jí)別為 public拉盾,就通過(guò)反射設(shè)置屬性值。所以說(shuō)豁状,Dubbo中的IOC僅支持以setter方式注入捉偏。
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
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);
}
return instance;
}
2.2、Wrapper
它會(huì)將當(dāng)前 instance 作為參數(shù)傳給 Wrapper 的構(gòu)造方法泻红,并通過(guò)反射創(chuàng)建 Wrapper 實(shí)例夭禽。 然后向 Wrapper 實(shí)例中注入依賴,最后將 Wrapper 實(shí)例再次賦值給 instance 變量谊路。說(shuō)起來(lái)可能比較繞讹躯,我們直接看下它最后生成的對(duì)象就明白了。
我們以DubboProtocol為例,它包裝后的對(duì)象為:
綜上所述潮梯,如果我們獲取一個(gè)擴(kuò)展類對(duì)象骗灶,最后拿到的就是這個(gè)Wrapper類的實(shí)例。
就像這樣:
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol extension = extensionLoader.getExtension("dubbo");
System.out.println(extension);
輸出為:com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper@4cdf35a9
3秉馏、獲取所有的擴(kuò)展類
在我們通過(guò)名稱獲取擴(kuò)展類對(duì)象之前矿卑,首先需要根據(jù)配置文件解析出所有的擴(kuò)展類。
它是一個(gè)擴(kuò)展點(diǎn)名稱和擴(kuò)展類的映射表Map<String, Class<?>>
首先沃饶,還是從緩存中cachedClasses
獲取母廷,如果沒(méi)有就調(diào)用loadExtensionClasses
從配置文件中加載。配置文件有三個(gè)路徑:
META-INF/services/
META-INF/dubbo/
META-INF/dubbo/internal/
先嘗試從緩存中獲取糊肤。
private Map<String, Class<?>> getExtensionClasses() {
//從緩存中獲取
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//加載擴(kuò)展類
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
如果沒(méi)有琴昆,就調(diào)用loadExtensionClasses
從配置文件中讀取。
private Map<String, Class<?>> loadExtensionClasses() {
//獲取 SPI 注解馆揉,這里的 type 變量是在調(diào)用 getExtensionLoader 方法時(shí)傳入的
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);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension
name on extension " + type.getName()+ ": " + Arrays.toString(names));
}
//設(shè)置默認(rèn)的擴(kuò)展名稱业舍,參考getDefaultExtension 方法
//如果名稱為true,就是調(diào)用默認(rèn)擴(kuò)贊類
if (names.length == 1) cachedDefaultName = names[0];
}
}
//加載指定路徑的配置文件
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
以Protocol接口為例升酣,獲取到的實(shí)現(xiàn)類集合如下舷暮,我們就可以根據(jù)名稱加載具體的擴(kuò)展類對(duì)象。
{
registry=class com.alibaba.dubbo.registry.integration.RegistryProtocol
injvm=class com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
thrift=class com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
mock=class com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=class com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=class com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
redis=class com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rmi=class com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
}
三噩茄、自適應(yīng)擴(kuò)展機(jī)制
在Dubbo中下面,很多拓展都是通過(guò) SPI 機(jī)制進(jìn)行加載的,比如 Protocol绩聘、Cluster沥割、LoadBalance 等。這些擴(kuò)展并非在框架啟動(dòng)階段就被加載凿菩,而是在擴(kuò)展方法被調(diào)用的時(shí)候机杜,根據(jù)URL對(duì)象參數(shù)進(jìn)行加載。
那么衅谷,Dubbo就是通過(guò)自適應(yīng)擴(kuò)展機(jī)制來(lái)解決這個(gè)問(wèn)題椒拗。
自適應(yīng)拓展機(jī)制的實(shí)現(xiàn)邏輯是這樣的:
首先 Dubbo 會(huì)為拓展接口生成具有代理功能的代碼。然后通過(guò) javassist 或 jdk 編譯這段代碼获黔,得到 Class 類蚀苛。最后再通過(guò)反射創(chuàng)建代理類,在代理類中肢执,就可以通過(guò)URL對(duì)象的參數(shù)來(lái)確定到底調(diào)用哪個(gè)實(shí)現(xiàn)類枉阵。
1译红、Adaptive注解
在開(kāi)始之前预茄,我們有必要先看一下與自適應(yīng)拓展息息相關(guān)的一個(gè)注解,即 Adaptive 注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
從上面的代碼中可知耻陕,Adaptive 可注解在類或方法上拙徽。
- 標(biāo)注在類上
Dubbo 不會(huì)為該類生成代理類。 - 標(biāo)注在方法上
Dubbo 則會(huì)為該方法生成代理邏輯诗宣,表示當(dāng)前方法需要根據(jù) 參數(shù)URL 調(diào)用對(duì)應(yīng)的擴(kuò)展點(diǎn)實(shí)現(xiàn)膘怕。
2、獲取自適應(yīng)拓展類
getAdaptiveExtension 方法是獲取自適應(yīng)拓展的入口方法召庞。
public T getAdaptiveExtension() {
// 從緩存中獲取自適應(yīng)拓展
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
//未命中緩存岛心,則創(chuàng)建自適應(yīng)拓展,然后放入緩存
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);
}
}
}
}
}
return (T) instance;
}
getAdaptiveExtension
方法首先會(huì)檢查緩存篮灼,緩存未命中忘古,則調(diào)用 createAdaptiveExtension
方法創(chuàng)建自適應(yīng)拓展。
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);
}
}
這里的代碼較少诅诱,調(diào)用 getAdaptiveExtensionClass
方法獲取自適應(yīng)拓展 Class 對(duì)象髓堪,然后通過(guò)反射實(shí)例化,最后調(diào)用injectExtension
方法向拓展實(shí)例中注入依賴娘荡。
獲取自適應(yīng)擴(kuò)展類過(guò)程如下:
private Class<?> getAdaptiveExtensionClass() {
//獲取當(dāng)前接口的所有實(shí)現(xiàn)類
//如果某個(gè)實(shí)現(xiàn)類標(biāo)注了@Adaptive干旁,此時(shí)cachedAdaptiveClass不為空
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//以上條件不成立,就創(chuàng)建自適應(yīng)拓展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
在上面方法中炮沐,它會(huì)先獲取當(dāng)前接口的所有實(shí)現(xiàn)類争群,如果某個(gè)實(shí)現(xiàn)類標(biāo)注了@Adaptive
,那么該類就被賦值給cachedAdaptiveClass
變量并返回大年。如果沒(méi)有祭阀,就調(diào)用createAdaptiveExtensionClass
創(chuàng)建自適應(yīng)拓展類。
3鲜戒、創(chuàng)建自適應(yīng)拓展類
createAdaptiveExtensionClass
方法用于生成自適應(yīng)拓展類专控,該方法首先會(huì)生成自適應(yīng)拓展類的源碼,然后通過(guò) Compiler 實(shí)例(Dubbo 默認(rèn)使用 javassist 作為編譯器)編譯源碼遏餐,得到代理類 Class 實(shí)例伦腐。
private Class<?> createAdaptiveExtensionClass() {
//構(gòu)建自適應(yīng)拓展代碼
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
// 獲取編譯器實(shí)現(xiàn)類 這個(gè)Dubbo默認(rèn)是采用javassist
Compiler compiler =ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
//編譯代碼,返回類實(shí)例的對(duì)象
return compiler.compile(code, classLoader);
}
在生成自適應(yīng)擴(kuò)展類之前失都,Dubbo會(huì)檢查接口方法是否包含@Adaptive
柏蘑。如果方法上都沒(méi)有此注解,就要拋出異常粹庞。
if (!hasAdaptiveAnnotation){
throw new IllegalStateException(
"No adaptive method on extension " + type.getName() + ",
refuse to create the adaptive class!");
}
我們還是以Protocol接口為例咳焚,它的export()
和refer()
方法,都標(biāo)注為@Adaptive
庞溜。destroy
和 getDefaultPort
未標(biāo)注 @Adaptive
注解革半。Dubbo 不會(huì)為沒(méi)有標(biāo)注 Adaptive 注解的方法生成代理邏輯碑定,對(duì)于該種類型的方法,僅會(huì)生成一句拋出異常的代碼又官。
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();
}
所以說(shuō)當(dāng)我們調(diào)用這兩個(gè)方法的時(shí)候延刘,會(huì)先拿到URL對(duì)象中的協(xié)議名稱,再根據(jù)名稱找到具體的擴(kuò)展點(diǎn)實(shí)現(xiàn)類六敬,然后去調(diào)用碘赖。下面是生成自適應(yīng)擴(kuò)展類實(shí)例的源代碼:
package com.viewscenes.netsupervisor.adaptive;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;
import com.alibaba.dubbo.rpc.RpcException;
public class Protocol$Adaptive implements Protocol {
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException(
"method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!");
}
public Exporter export(Invoker invoker)throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("Invoker argument getUrl() == null");
}
URL url = invoker.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) {
throw new IllegalStateException("Fail to get extension(Protocol) name from url("
+ url.toString() + ") use keys([protocol])");
}
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return extension.export(invoker);
}
public Invoker refer(Class clazz,URL ur)throws RpcException {
if (ur == null) {
throw new IllegalArgumentException("url == null");
}
URL url = ur;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) {
throw new IllegalStateException("Fail to get extension(Protocol) name from url("+ url.toString() + ") use keys([protocol])");
}
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return extension.refer(clazz, url);
}
}
綜上所述,當(dāng)我們獲取某個(gè)接口的自適應(yīng)擴(kuò)展類外构,實(shí)際就是一個(gè)Adaptive
類實(shí)例普泡。
ExtensionLoader<Protocol> extensionLoader =
ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol adaptiveExtension = extensionLoader.getAdaptiveExtension();
System.out.println(adaptiveExtension);
輸出為:
com.alibaba.dubbo.rpc.Protocol$Adaptive@47f6473
四、實(shí)例
我們看完以上流程之后审编,如果想寫一套自己的邏輯替換Dubbo中的流程劫哼,就變得很簡(jiǎn)單。
Dubbo默認(rèn)使用dubbo
協(xié)議來(lái)暴露服務(wù)割笙。我們可以搞一個(gè)自定義的協(xié)議來(lái)替換它权烧。
1、實(shí)現(xiàn)類
首先伤溉,我們創(chuàng)建一個(gè)MyProtocol
類般码,它實(shí)現(xiàn)Protocol接口。
package com.viewscenes.netsupervisor.protocol;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol;
public class MyProtocol extends DubboProtocol implements Protocol{
public int getDefaultPort() {
return 28080;
}
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
System.out.println("自定義協(xié)議乱顾,進(jìn)行服務(wù)暴露:"+url);
return super.export(invoker);
}
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return super.refer(type, url);
}
public void destroy() {}
}
2板祝、擴(kuò)展點(diǎn)配置文件
然后,在自己的項(xiàng)目中META-INF/services
創(chuàng)建com.alibaba.dubbo.rpc.Protocol
文件走净,文件內(nèi)容為:
myProtocol=com.viewscenes.netsupervisor.protocol.MyProtocol
3券时、修改Dubbo配置文件
最后修改生產(chǎn)者端的配置文件:
<!-- 用自定義協(xié)議在20880端口暴露服務(wù) -->
<dubbo:protocol name="myProtocol" port="20880"/>
這樣在我們啟動(dòng)生產(chǎn)者端項(xiàng)目的時(shí)候,Dubbo在進(jìn)行服務(wù)暴露的過(guò)程中伏伯,就會(huì)調(diào)用到我們自定義的MyProtocol
類橘洞,完成相應(yīng)的邏輯處理。