Dubbo里除了Service和Config层为API,其它各层均为SPI。相比于JAVA中的SPI仅仅通过接口类名获取所有实现,Dubbo的实现可以通过接口类名和key值来获取一个具体的实现。通过SPI机制,Dubbo实现了面向插件编程,只定义了模块的接口,实现由各插件来完成。
在扩展类的jar包内,放置扩展点配置文件META-INF/service/接口全限定名,内容为:扩展实现类全限定名,多个实现类用换行符分隔。
如下为MySQL中Driver接口的实现:
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
... }
调用时使用ServiceLoader加载所有的实现并通过循环来找到目标实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();try{
while(driversIterator.hasNext()) {
driversIterator.next(); }} catch(Throwable t) {
// Do nothing
}
拿Dubbo中Protocol接口来说,Protocol的定义如下:
package org.Apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.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();
}
需要指出的是Invoker继承了Node接口,而Node接口提供了getUrl方法,既每个方法都能从入参获得URL对象。
public interface Node {
URL getUrl();
boolean isAvailable();
void destroy();
}
要求
1.接口上有org.apache.dubbo.common.extension.SPI注解,提供默认的实现
2.对于支持自适应扩展的方法要求方法入参能获得org.apache.dubbo.common.URL对象,同时方法上有org.apache.dubbo.common.extension.Adaptive注解,Adaptive注解可以提供多个key名,以便从URL中获取对应key的值,从而匹配到对应的实现(这里Protocol比较特别,没有提供key名也能根据URL来动态获取实现,后面会说明)
3.在扩展类的jar包内,放置扩展点配置文件META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。如Protocol的默认实现:
注意:META-INF/dubbo/internal为Dubbo内部实现的配置文件路径
调用时通过ExtensionLoader根据需要来选择具体的实现类,
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
可选方式包括
1.选择默认的实现
Protocol protocol = loader.getDefaultExtension();
2.根据指定key选择实现
Protocol protocol = loader.getExtension("dubbo");
3.根据URL参数动态获取实现
Protocol protocol = loader.getAdaptiveExtension();
Dubbo对Java中的标准SPI进行了扩展增强,官方文档中提到其提供了如下特性:
在介绍各个特性前先介绍下大概的内部实现。从上面的使用中可以看到Dubbo对SPI扩展的主要实现在ExtensionLoader类中,关于这个类源码的讲解可以看官方文档,讲解的很详细,这边主要说下大概过程:
在返回真正实例前,会用该类型的包装类进行包装,既采用装饰器模式进行功能增强。
if (CollectionUtils.isNotEmpty(wrApperClasses)) {
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
其实就是使用静态代理来实现AOP
返回实例前会遍历所有的setXXX方法,判断set方法参数是否存在自适应对象,如果存在则通过ExtensionLoader加载自适应对象然后进行赋值,可以通过方法上加org.apache.dubbo.common.extension.DisableInject注解来屏蔽该功能。该功能其实就是实现了IOC,具体可以看ExtensionLoader的injectExtension方法。
同上面介绍的一样,Dubbo的SPI可以根据传入的URL参数中携带的数据来动态选择具体的实现。
上面介绍过,Adaptive注解是加在方法上的,类似的有个注解org.apache.dubbo.common.extension.Activate是加在实现类上。当加在Class上时,ExtensionLoader会将对应的key和Class信息缓存到另一个容器中,后续可以通过ExtensionLoader获取某一类的实现列表,既如下方法
public List<T> getActivateExtension(URL url, String[] values){
...}
ExtensionLoader自适应扩展机制的大概实现逻辑是这样的:Dubbo会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,在代理类中,就可以通过URL对象的参数来确定到底调用哪个实现类。主要实现在createAdaptiveExtensionClass方法中。
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);
}
上面的Protocol接口经过处理后的内容如下:
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
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.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 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);
}}
前面提到过,Protocol是一个比较特殊的接口,Adaptive注解里没有指定Key,因而没法通过URL携带的参数获取具体的实现类。从上面处理过后的内容来看,可以看到,对于Protocol接口直接使用的URL的protocol属性字段的值来进行判断的。
如下例子是在Adaptive里有提供key的实现处理过后的内容:
public java.lang.String sayHello(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
} org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("keyA", "dobbo");
if (extName == null) {
throw new IllegalStateException("Failed to get extension (cn.demo.spi.Factory) name from url (" + url.toString() + ") use keys([keyA])");
} cn.demo.spi.Factory extension = (cn.demo.spi.Factory) ExtensionLoader.getExtensionLoader(cn.demo.spi.Factory.class).getExtension(extName);
return extension.sayHello(arg0);
}
可以发现这种情况直接使用的URL的getParameter方法从携带的参数中获取对应的值。
通过查看AdaptiveClassCodeGenerator的实现可以发现该类的generateExtNameAssignment里对protocol做了特殊的判断。AdaptiveClassCodeGenerator完成了代理类内容的创建,大概过程为:根据原接口定义(type),包装出根据URL参数动态调用getExtension方法的实现类,然后将动作实际委托给了ExtensionLoader.getExtensionLoader(type).getExtension(extName);方法。其中extName为方法上的Adaptive注解指定的key的对应值,获取过程为:
Protocol接口由于没有在Adaptive注解里指定key,则会使用类名protocol作为默认的扩展名,从而命中第2条规则。
通过自适应扩展机制,Dubbo框架的各层的核心实现都是基于接口的,而将具体的实现下放到插件中。根据加载的插件不同,各层能够选择适合的实现而不会影响核心的逻辑流程。如Protocol接口,表示通信协议,可选的实现包括dubbo、rmi、http等。