Dubbo SPI 扩展机制
一、理解
我的理解就是SPI扩展机制给予Dubbo带来了更多的灵活的扩展性,可以自动根据URL中的参数,进行选择合适的扩展类进行处理,除了这个简单的Adapter 帮助适配之外,还引入了IOC机制,set and 构造函数;使用URL参数进行选择合适的扩展为默认的Adapter,还可以通过自己手动的Adaptive 这个注解进行自定义呢。功能十分的丰富,代码也不是非常的多,按需加载特定的SPI的扩展,性能不是问题。
spi :可以理解为,一个接口有多个实现类,我们需要根据某种策略进行选择具体的实现,Dubbo中主要根据URL 参数进行传递,比如DynamicConfigurationFactory 动态配置中心,有Etcd、Nacos、Zookeeper的实现,这里是根据协议的类型去选择一个具体的实现
直接根据某个key 去或者扩展
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
注解SPI 意思就是说,这个类要被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);