Dubbo SPI 源码

226 阅读6分钟

主要注解

SPI

@SPI 注解可以使用在类、接口和枚举类上,Dubbo 框架中都是使用在接口上。它的主要作用就是标记这个接口是一个Dubbo SPI 接口,即是一个扩展点,可以有多个不同的内置或用户定义的实现。运行时需要通过配置找到具体的实现类。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    String value() default "";
}

通过 value 属性,可以传入不同的参数来设置这个接口的默认实现类。

Adaptive

博客示例 http://t.csdn.cn/3OIpQ

@Adaptive 注解可以标记在类、接口、枚举类和方法上,在整个 Dubbo框架中,只有 AdaptiveExtensionFactory和AdaptiveCompiler 等使用在类级别上,其余都标注在方法上。

如果标注在接口的方法上,即方法级别注解,则可以通过参数动态获得实现类。方法级别注解在第一次 getExtension 时,会自动生成和编译一个动态的 Adaptive 类,从而达到动态实现类的效果。

下面是自动生成的 Transporter$Adaptive#bind实现代码。

public Server bind(URL arg0, ChannelHandler argl)throws RemotingException (
	...
	org.apache.dubbo.common.URL url = arg0;
	// 通过 @Adaptive 注解中的两个 key 去寻找实现类的名称
	String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
	...
	try {
		// 根据URL中的参数,尝试获取真正的扩展点实现类
		extension = (org.apache.dubbo.remoting.Transporter)ExtensionLoader
			.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class)
			.getExtension(extName);
	} catch(Exception e) {
		...
		// 如果获取失败,则使用SPI注解中指定的默认实现Netty
		extension = (org.apache.dubbo.remoting.Transporter) ExtensionLoader
			.getExtensionLoader(org, apache dubbo.remoting.Transporter.class)
			.getExtension("netty");
	}
	// 最终会调用具体扩展点实现类的bind方法
	return extension.bind(arg0, argl);
}

当该注解放在实现类上,则整个实现类会直接作为默认实现,不再自动生成上述代码。在扩展点接口的多个实现里,只能有一个实现上可以加 @Adaptive 注解。如果多个实现类都有该注解则会抛出异常。

@Adaptive 注解的源代码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {

    String[] value() default {};
}

Adaptive 可以传入多个 key 值,在初始化 Adaptive 注解的接口时,会先对传入的 URL 进行 key 值匹配,第一个key没匹配上则匹配第二个,以此类推。直到所有的key匹配完毕,如果还没有匹配到, 则会使用驼峰规则匹配,如果也没匹配到,则会抛出 IllegalStateException 异常。

@Adaptive 放在实现类上,主要是为了直接固定对应的实现而不需要动态生成代码实现,就像策略模式直接确定实现类。实现方式:ExtensionLoader 中会缓存两个与 @Adaptive 有关的对象,一个缓存在 cachedAdaptiveClass 中, 即 Adaptive 具体实现类的 Class 类型。另外一个缓存在 cachedAdaptivelnstance 中,即 Class 的具体实例化对象。在扩展点初始化时,如果发现实现类有 @Adaptive 注解,则直接赋值给 cachedAdaptiveClass,后续实例化类的时候,就不会再动态生成代码,直接实例化 cachedAdaptiveClass,并把实例缓存到 cachedAdaptivelnstance 中。如果注解在接口方法上, 则会根据参数,动态获得扩展点的实现,会生成 Adaptive 类,再缓存到 cachedAdaptivelnstance 中。

Activate

@Activate 可以标记在类、接口、枚举类和方法上。主要使用在有多个扩展点实现、需要根据不同条件被激活的场景中,如Filter 需要多个同时激活,因为每个 Filter 实现的是不同的功能。Activate 参数如下。

String[]  group() 	URL 中的分组如果匹配则激活,则可以设置多个
String[]  value()	查找 URL 中如果含有该 key 值,则会激活
String[]  before()	填写扩展点列表,表示哪些扩展点要在本扩展点之前
String[]  after()	同上,表示哪些需要在本扩展点之后
int       order()	整型,直接的排序信息

拓展点特性

扩展类一共包含四种特性:自动包装、自动加载、自适应和自动激活。

1)自动包装
自动包装是一种被缓存的扩展类,ExtensionLoader 在加载扩展时,如果发现这个扩展类包含其他扩展点作为构造函数的参数,则这个扩展类就会被认为是 Wrapper 类。

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
    ....
}

ProtocolFilterWrapper 虽然继承了 Protocol 接口,但是其构造函数中又注入了一个 Protocol 类型的参数。因此ProtocolFilterWrapper 会被认定为 Wrapper 类。这是一种装饰器模式,把通用的抽象逻辑进行封装或对子类进行增强,让子类可以更加专注具体的实现。

2)自动加载
除了在构造函数中传入其他扩展实例,我们还经常使用 setter 方法设置属性值。如果某个扩展类是另外一个扩展点类的成员属性,并且拥有 setter 方法,那么框架也会自动注入对应的扩展点实例。ExtensionLoader 在执行扩展点初始化的时候,会自动通过 setter方法注入对应的实现类。如果扩展类属性是一个接口,它有多种实现,具体注入哪一个实现类就涉及第三个特性一一自适应。

3)自适应
在 Dubbo SPI 中,使用 @Adaptive 注解,可以动态地通过 URL 中的参数来确定要使用哪个具体的实现类。从而解决自动加载中的实例注入问题。©Adaptive 注解使用示例。

@SPI("netty")
public interface Transporter {

    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

@Adaptive 传入了两个 Constants 中的参数,它们的值分别是 server 和 transporter 。当外部调用 Transporter#bind方法时,会动态从传入的参数 URL 中提取 key 参数 server 的value值,如果能匹配上某个扩展实现类则直接使用对应的实现类;如果未匹配上,则继续通过第二个key参数 transporter 提取 valu值。如果都没匹配上,则抛出异常。即 @Adaptive 中传入了多个参数,则依次进行实现类的匹配,直到最后抛出异常。

4)自动激活
使用 @Activate 注解,可以标记对应的扩展点默认被激活启用。该注解还可以通过传入不同的参数,设置扩展点在不同的条件下被自动激活。主要的使用场景是某个扩展点的多个实现类需要同时启用(比如 Filter 扩展点)。

ExtensionLoader

每个接口有一个对应的ExtensionLoader,包含如下成员变量

public class ExtensionLoader<T> {

    //配置文件预设目录
    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/";

    //静态变量,存储工厂模式创建的各接口对应的ExtensionLoader
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

    //存储各个接口实现类对应的实例
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    //接口类型
    private final Class<?> type;

    private final ExtensionFactory objectFactory;

    //key为拓展类,value为配置文件中拓展名
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    //key为配置文件中拓展名,value为拓展类
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    //key为配置文件中name,值为Activate注解
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
    //拓展实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    //Adaptive类缓存
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    private volatile Class<?> cachedAdaptiveClass = null;
    //SPI注解中的值作为默认name
    private String cachedDefaultName;
    //记录是否创建AdaptiveInstance是否发生过错误
    private volatile Throwable createAdaptiveInstanceError;

    //包装类缓存
    private Set<Class<?>> cachedWrapperClasses;

    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
}

文件加载、缓存

加载并缓存拓展文件.png 加载并缓存拓展文件.png

getExtension

SPI getExtension.png SPI getExtension.png

ExtensionFactory

SPI ExtendFactory接口实现类.png SPI ExtendFactory接口实现类.png

getAdaptiveExtension

SPI getAdaptiveExtension.png SPI getAdaptiveExtension.png

getActivateExtension

SPI getActivateExtension .png SPI getActivateExtension .png