Dubbo SPI 擴展機制
一、理解
我的理解就是SPI擴展機制給予Dubbo帶來了更多的靈活的擴展性,可以自動根據URL中的參數,進行選擇合適的擴展類進行處理,除了這個簡單的Adapter 幫助適配之外,還引入了IOC機制,set and 構造函數;使用URL參數進行選擇合適的擴展爲默認的Adapter,還可以通過自己手動的Adaptive 這個註解進行自定義呢。功能十分的豐富,代碼也不是非常的多,按需加載特定的SPI的擴展,性能不是問題。
spi :可以理解爲,一個接口有多個實現類,我們需要根據某種策略進行選擇具體的實現,Dubbo中主要根據URL 參數進行傳遞,比如DynamicConfigurationFactory 動態配置中心,有Etcd、Nacos、Zookeeper的實現,這裏是根據協議的類型去選擇一個具體的實現
1、根據key 獲取擴展
DynamicConfigurationFactory factories = ExtensionLoader
.getExtensionLoader(DynamicConfigurationFactory.class)
.getExtension(url.getProtocol());
2、根據一個適配類,適配類根據參數進行動態獲取擴展
如下獲取一個適配類
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
然後根據URL的參數,類似根據key獲取擴展。
public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) 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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0, arg1);
}
org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocolTest#testThriftProtocol
@Test
public void testThriftProtocol() throws TException{
DemoServiceImpl server = new DemoServiceImpl();
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
URL url = URL.valueOf(org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol.NAME + "://127.0.0.1:5341/" + DemoService.Iface.class.getName() + "?version=1.0.0&nativethrift.overload.method=true");
Exporter<DemoService.Iface> exporter = protocol.export(proxyFactory.getInvoker(server, DemoService.Iface.class, url));
Invoker<DemoService.Iface> invoker = protocol.refer(DemoService.Iface.class, url);
DemoService.Iface client = proxyFactory.getProxy(invoker);
String result = client.sayHello("haha");
Assertions.assertTrue(server.isCalled());
Assertions.assertEquals("Hello, haha", result);
invoker.destroy();
exporter.unexport();
}
二、如何學習 Dubbo SPI
1、dubbo sample & 源碼
git clone https://github.com/apache/dubbo-samples.git
2、斷點跟蹤 & 源碼測試用例
建議:源碼跟蹤畢竟代碼量比較大,充足的時間、充足的經歷、然後加上註釋,不要害怕。
三、Dubbo SPI 關鍵解析
1、@SPI
2、@Active
Active 可以理解爲激活的意思,getExtension(name)是根據配置文件中定義的key 獲取到唯一的Class,而Active可以理解爲篩選的意思,這麼多的擴展,可以根據分組可values去篩選出所有的擴展,這個是賽選多個的意思,然後在根據getExtension(name)或者所有的擴展的實例。
List getActivateExtension(URL url, String[] values, String group)
3、@Adaptive
3.1 默認適配類
我的理解是根據參數動態的選擇某種擴展,默認情況下定義的SPI 會生成一個默認的擴展,這個擴展是根據URL中的參數進行選擇的,也可以自己聲明一個擴展哦。@Adaptive 註解只是針對適配類有效,標註到類上標明這個類就是一個適配不使用默認的,其他的就是使用默認的,使用默認的最少要有一個@Adaptive({PROXY_KEY}),註解在方法上,根據參數動態的獲取到適配的SPI的實現類。
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
/**
* create proxy.
*
* @param invoker
* @return proxy
*/
@Adaptive({PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
斷點設置到這裏會看的自動生成的代碼;
org/apache/dubbo/common/extension/ExtensionLoader.java:996
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
public java.lang.Object getProxy(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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) 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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0, arg1);
}
public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
if (arg2 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
}
3.2 自定義適配類
自定義的@Adaptive,在Dubbo中就有一個AdaptiveExtensionFactory,他的策略是根據先後順序,找到了就返回,就兩種情況。
**
* AdaptiveExtensionFactory 依賴注入工廠
*/
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
// 依賴注入 根據第一匹配原則去查找,如果SPI中有 使用 SpiExtensionFactory,如果Spring 容器中有 使用Spring 容器中的!
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
3.3 不適用自定義適配類
還有不使用@Adaptive DynamicConfigurationFactory動態配置中心,接口上沒有定義動態適配,只能根據名稱進行一個個的獲取,不能調用getAdaptiveExtension這個方法,這裏會觸發自動生成適配默認的適配類的,接口上並沒有定義,因此會報錯
org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate
DynamicConfigurationFactory factories = ExtensionLoader
.getExtensionLoader(DynamicConfigurationFactory.class)
.getExtension(url.getProtocol());
/**
* 默認是什麼都沒有實現的
*/
@SPI("nop")
public interface DynamicConfigurationFactory {
/**
* 根據SPI動態獲取配置中心
* @param url
* @return
*/
DynamicConfiguration getDynamicConfiguration(URL url);
}
4、@DisableInject
不允許進行依賴注入,依賴注入可以分爲IOC的set 、構造依賴注入,標註這個表明不進行依賴注入處理。
假設協議是Zookeeper,那麼這個ZookeeperTransporter是怎麼注入依賴的?
DynamicConfigurationFactory factories = ExtensionLoader
.getExtensionLoader(DynamicConfigurationFactory.class)
.getExtension(url.getProtocol());
org.apache.dubbo.common.extension.ExtensionLoader#createExtension
4.1 這裏是Adaptive中的注入工廠處理。
org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
4.2 根據Class 類名稱獲取AdaptiveExtension
/**
* SpiExtensionFactory 處理相關的依賴
*/
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
//依賴注入必須實現 Adaptive
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
public class ZookeeperDynamicConfigurationFactory extends AbstractDynamicConfigurationFactory {
/**
* Zookeeper 也是動態生成的擴展
*/
private ZookeeperTransporter zookeeperTransporter;
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
protected DynamicConfiguration createDynamicConfiguration(URL url) {
return new ZookeeperDynamicConfiguration(url, zookeeperTransporter);
}
}
@SPI("curator")
public interface ZookeeperTransporter {
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
ZookeeperClient connect(URL url);
}
四、源碼流程解析
一個SPI擴展對應一個ExtensionLoader,所有的ExtensionLoader都有ExtensionFactory依賴注入處理工廠,一個SPI對應一個Adaptive(可能是默認的、或者自定義的)
1、無需Adaptive,直接根據key進行獲取。
DynamicConfigurationFactory factories = ExtensionLoader
.getExtensionLoader(DynamicConfigurationFactory.class)
.getExtension(url.getProtocol());
2、獲取Adaptive
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
3、獲取@Active getActivateExtension
比如:Filter過濾的時候需要獲取某種分組情況下的Filter ,這個服務提供者,還是服務消費者提供的呢?因此就有了分組的概念。
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);