最近看Dubbo源碼的時候,最開始對Dubbo的自適應擴展一直沒怎么看明白,參考其他的博客大多也就是把官方的代碼解釋搬過來,然并卵。
對SPI不明白可以參考官方文檔
最后按照自己的理解來模擬了一下,希望對大家能有所幫助。
先定義接口類 SpiTest,有一個mySpi方法:
@SPI //標記為擴展接口public interface SpiTest{
void mySpi(URL url, String name);
}
有兩個實現(xiàn)類S1和S2:
public class S1 implements SpiTest{
@Override
public void mySpi(URL url, String name) {
System.out.println("This is S1 : "+name);
}
}
public class S2 implements SpiTest{
@Override
public void mySpi(URL url,String name) {
System.out.println("This is S2 : "+name);
}
}
在META-INF/dubbo下創(chuàng)建文件com.xx.dubbospi.SpiTest
S1=com.zf.xx.dubbospi.S1
S2=com.zf.xx.dubbospi.S2
在正常使用的時候可以通過 ExtensionLoader.getExtensionLoader(SpiTest.class).getExtension("S1")來獲取SpiTest的某一個實現(xiàn),但是如果在方法調(diào)用時不確定具體實現(xiàn)類怎么辦?可以定義一個包裝類SpiWrapper ,包裝類不具體實現(xiàn)方法,只是根據(jù)參數(shù)獲取對應的擴展對象來執(zhí)行,根據(jù)傳入的參數(shù)來獲取到底時S1還是S2:
public class SpiWrapper implements SpiTest{
@Override
public void mySpi(URL url,String name) {
SpiTest spiTest = ExtensionLoader.getExtensionLoader(SpiTest.class).getExtension(url.getParameter("spi.test"));//通過參數(shù)指定需要加載的對象
spiTest.mySpi(url,name);
}
public static void main(String[] args) {
URL url = new URL("dubbo","123",999);//這里的URL是 org.apache.dubbo.common.URL
url = url.addParameter("spi.test","S2");
SpiWrapper spiWrapper = new SpiWrapper();
spiWrapper.mySpi(url,"tudou");
}
}
在SpiWrapper 中會根據(jù)url上的參數(shù)spi.test的值類決定到底取SpiTest的哪一個實現(xiàn)類,這樣就實現(xiàn)的SPI的一個動態(tài)擴展。而在Dubbo中具體的使用需要先對SpiTest進行改造:
@SPIpublic interface SpiTest{
@Adaptive
void mySpi(URL url, String name);
}
在方法上增加注解 @Adaptive,Adaptive就是告訴Dubbo應該使用哪一個實現(xiàn)類來調(diào)用mySpi方法。實現(xiàn)邏輯就是通過約定在URL(key-value)中提取 key值,通過key值來決定實現(xiàn)類。比如我們使用 protocol為Dubbo,那么通過yml文件指定dubbo.protocol.name為dubbo,在URL上的格式就是 protocol=dubbo,后續(xù)執(zhí)行服務流量交易導出 export的時候就會加載DubboProtocol來實現(xiàn)。
@SPI("dubbo") //默認dubbo
public interface Protocol {
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
配置文件
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
service-discovery-registry=org.apache.dubbo.registry.client.ServiceDiscoveryRegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
再來看我們的例子:
public class SpiWrapper implements SpiTest{
@Override
public void mySpi(URL url,String name) {
//主要通過在Url上找到 spi.test 參數(shù)的值 , ExtensionLoader.getExtensionLoader(SpiTest.class).getExtension("S1")
SpiTest spiTest = ExtensionLoader.getExtensionLoader(SpiTest.class).getAdaptiveExtension();//通過url參數(shù)獲取自適應對象
//SpiTest spiTest = ExtensionLoader.getExtensionLoader(SpiTest.class).getExtension(url.getParameter("spi.test"));
spiTest.mySpi(url,name);
}
public static void main(String[] args) {
URL url = new URL("dubbo","123",999);
url = url.addParameter("spi.test","S1");//指定url參數(shù)
SpiWrapper spiWrapper = new SpiWrapper();
spiWrapper.mySpi(url,"tudou");
}
}
通過指定URL的參數(shù),就可以自動加載對應的擴展實現(xiàn)類。具體的ExtensionLoader源碼分析,官網(wǎng)寫的很詳細,有興趣可以看一下。
Dubbo源碼中大量使用了SPI的動態(tài)擴展,如果不弄清楚,可能對學習源碼會是一個比較大的阻礙。希望這篇文章能幫助大家進一步的理解SPI的自適應,在進行源碼調(diào)試的時候,可以追蹤到具體的實現(xiàn)類。