上一篇关于dubbo的SPI基本介绍完毕了,针对SPI中一些细节点,专门开辟本文来做补充(补充点包括但不限于@Adaptive、@Activate、AdaptiveClassCodeGenerator的具体逻辑),后续发现有遗漏同样也会加入本篇。
一、@Adaptive的处理
@Adaptive通常与ExtensionLoader.getAdaptiveExtension方法配合使用,可用于SPI扩展类或者SPI接口方法,首先来看用于SPI扩展类。
用于SPI扩展类
加载SPI扩展类过程中,会扫描扩展类是否使用@Adaptive,若有,则初始化cachedAdaptiveClass(在此之前会完成cachedDefaultName的初始化),用于后面实例化SPI扩展类。除此之外,同一个接口的SPI扩展类中,有且只能有一个SPI扩展类使用@Adaptive,否则会直接抛异常。以ExtensionFactory为例,dubbo内置三种实现,AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory,其中只有AdaptiveExtensionFactory使用了Adaptive注解,所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()返回的是AdaptiveExtensionFactory的实例
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory
用于SPI接口方法
若SPI接口的所有实现类都没有使用@Adaptive注解,那么,会在SPI接口方法上寻找@Adaptive注解。若该SPI接口的所有方法上都没有注解(接口的所有SPI扩展类上也没有使用@Adaptive),则认为该SPI接口不支持Adaptive扩展;否则,会根据注解生成代理类源码并编译成class文件。以SimpleExt为例,
@SPI("impl1")
public interface SimpleExt {
// 不指定key
@Adaptive
String echo(URL url, String s);
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
// no @Adaptive
String bang(URL url, int i);
}
对应生成的Adaptive代理类代码如下:
package org.apache.dubbo.common.extension.ext1;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adaptive implements SimpleExt {
public String yell(org.apache.dubbo.common.URL arg0, String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
// 获取扩展类实例名,优先从url参数中取,没有则去默认实现
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
if(extName == null)
throw new IllegalStateException("Failed to get extension ( SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])");
//最终调用的还是getExtension方法
SimpleExt extension = (SimpleExt)ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
return extension.yell(arg0, arg1);
}
public String echo(org.apache.dubbo.common.URL arg0, String arg1) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
// @Adaptive注解没有参数,所以这里采用的规则是 优先取URL中名为simple.ext(根据SimpleExt接口名转换而来)的参数
String extName = url.getParameter("simple.ext", "impl1");
if(extName == null)
throw new IllegalStateException("Failed to get extension ( SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
SimpleExt extension = (SimpleExt)ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
// 非Adaptive方法,直接抛异常
public String bang(org.apache.dubbo.common.URL arg0, int arg1) {
throw new UnsupportedOperationException("The method public abstract String SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface SimpleExt is not adaptive method!");
}
}
可以看到,接口中的方法可以分为三种,下面依次来看
1、使用Adaptive注解,且value值为空
Adaptive代理类内部方法如下:先根据SPI接口生成参数名(举个例子,比如这里的SimpleExt.echo方法,生成的参数名为simple.ext),然后从URL中获取该参数值,作为方法内当前代理类的扩展实现名(假设这里url中参数simple.ext的值为"impl1"),为空则直接抛异常;接着,通过ExtensionLoader.getExtensionLoader().getExtension()方法,获取真正被代理的SPI实现,并最终调用该实现的方法。以SimpleExt接口的echo方法为例,整个过程用代码表示:
// 生成URL参数名
String param = "simple.ext";
// 获取当前SPI扩展实现名
String extName = url.getParameter(param,"impl1");
// 获取真正被代理的SPI实现
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
// 调用真实SPI实现类的方法
ext.echo();
2、使用Adaptive注解,且value值非空
注解value值非空的情况比较简单,与第一种情况相比,少了生成URL参数名这一步。直接将注解的value值作为key,从URL中获取当前SPI扩展实现名;这里比较有意思,若value只有一个,则要么是URL中参数名为value对应的SPI扩展实现或者默认的SPI扩展实现;若value值有多个,那么dubbo获取扩展实现类的优先级顺序与value值的顺序一致,比如@Adaptive("key1", "key2"),假设key1,key2对应的SPI扩展实现名分别为 "impl"和"impl2",dubbo使用真实扩展类的顺序(URL参数中key1,key2决定)会是"impl" -> "impl2" -> "默认SPI实现"。这里以SimpleExt.yell方法为例,整个过程用代码表示:
// @Adaptive("key1", "key2"),多值时支持嵌套,优先级与值顺序保持一致
// 获取当前SPI扩展实现名
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
// 获取真正被代理的SPI实现
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
// 调用真实SPI实现类的方法
ext.yell();
再来看Dispatcher接口的代理类代码,多值情况的处理更加清晰
package org.apache.dubbo.remoting;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Dispatcher$Adaptive implements org.apache.dubbo.remoting.Dispatcher {
public org.apache.dubbo.remoting.ChannelHandler dispatch(org.apache.dubbo.remoting.ChannelHandler arg0, org.apache.dubbo.common.URL arg1) {
if (arg1 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = url.getParameter("dispatcher",url.getParameter("dispather",url.getParameter("channel.handler", "all")));
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Dispatcher) name from url (" + url.toString() + ") use keys([dispatcher, dispather, channel.handler])");
org.apache.dubbo.remoting.Dispatcher extension = (org.apache.dubbo.remoting.Dispatcher)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Dispatcher.class).getExtension(extName);
return extension.dispatch(arg0, arg1);
}
}
可以看到这里Dispatcher的优先级顺序为:dispatcher > dispather > channel.handler > all,其中all是Dispatcher的默认SPI实现。
3、不使用Adaptive注解。
不使用@Adaptive注解的方法,则直接抛异常,比较简单。
最后对Adaptive注解做一个总结:Adaptive注解在dubbo SPI中扮演着非常重要的角色,是dubbo的SPI灵活实现的核心。这一特点在ExtensionLoader.getAdaptiveExtension方法中体现的淋漓尽致,尤其在@Adaptive注解用于方法时,最终生成的代理类完全支持方法级扩展,非常灵活,这是JDK的SPI所不能比的。
二、@Activate的处理
@Activate同样可用于类和方法(实际上并没看到用于方法,相关处理逻辑也没有),与@Adaptive不同,提供了基于group和value两个维度的SPI扩展实现。对Activate的处理逻辑全部在getActivateExtension方法(这里就不贴代码了),整体上根据value中是否包含"-default"分为两部分。
value中无"-default"
先来看value中不包含"-default"的情况,这种情况下,会直接加载SPI接口的所有扩展类,加载过程中会判断缓存SPI扩展类上是否使用了注解,若有则直接放入cachedActivates缓存(注意,只有在注解的value中没有"-default"时才会用到cachedActivates),缓存格式如下:
// 其中key是SPI扩展类对应的扩展名,value则是该SPI扩展类上的注解实例,比如<"spi",Activate@hashCode>
Map cachedActivates = new HashMap();
然后通过cachedActivates跟方法参数中的group进行匹配,匹配过程如下:遍历cachedActivates缓存,匹配参数group与缓存实例的group值;再根据方法参数中的value值与cachedActivates缓存的SPI扩展名匹配,加上url中的参数与cachedActivates缓存中注解的value匹配,group与value都匹配的情况下,才会放入SPI扩展类实例结果集,并排序。听起来可能比较复杂,这里同样以dubbo中提供的demo ActivateExt1接口为例来分析,接口对应有多个SPI扩展类,对应的SPI配置如下:
group=org.apache.dubbo.common.extension.activate.impl.GroupActivateExtImpl
value=org.apache.dubbo.common.extension.activate.impl.ValueActivateExtImpl
order1=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl1
order2=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl2
old1=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl2
old2=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl3
各扩展类的定义如下:
// 默认order值为0,按照order值倒序排序
@Activate(group = {"group1", "group2"})
public class GroupActivateExtImpl implements ActivateExt1
@Activate(value = {"value"}, group = {"value"})
public class ValueActivateExtImpl implements ActivateExt1
@Activate(order = 1, group = {"order"})
public class OrderActivateExtImpl1 implements ActivateExt1
@Activate(order = 2, group = {"order"})
public class OrderActivateExtImpl2 implements ActivateExt1
@Activate(group = "old_group")
public class OldActivateExt1Impl2 implements ActivateExt1
@Activate(group = "old_group")
public class OldActivateExt1Impl3 implements ActivateExt1
SPI扩展类加载完毕后,cachedActivates中内容如下(方便起见,用伪码表示)
("group",@Activate(group = {"group1", "group2"}));
("value",@Activate(value = {"value"}, group = {"value"}));
("order1",@Activate(order = 1, group = {"order"}));
("order2",@Activate(order = 2, group = {"order"}));
("old1",@Activate(group = "old_group"));
("old2",@Activate(group = "old_group"))
此时,调用getActivateExtension(URL,"","value"),方法,假设url中参数"value"或者"*.value"值为"value",那么结果将会是value对应的SPI扩展类即ValueActivateExtImpl。
value中有"-default"
当value中有"-default"时,注解中group值会完全被忽略,仅会根据value中的值,是否带有remove标志(即 "-")将SPI扩展类实例加入结果集;注意,这里不会进行排序,同样以ActivateExt接口为例,此时调用getExtension(url,"ext","value"),假设url中参数"ext"值为"-default,value",那么结果同样是value对应的SPI扩展类实现即ValueActiveExtImpl。
可以看到@Actiavte注解的处理逻辑与Adaptive完全不同,主要区别在于Adaptive提供SPI扩展类实现的代理,而Activate则通过注解参数以及URL参数过滤符合要求的SPI扩展类实现。二者之间同样存在联系,@Adaptive优先级要高于@Activate,也就是说,如果一个SPI扩展类实现上同时使用这两个注解,那么,会优先处理@Adaptive,而忽略对@Activate的处理(此时getActivateExtension()基本上已经退化为getExtension()),而且,此时getAdaptiveExtension()的逻辑不受影响。
三、Wrapper代理
前面介绍过ExtensionLoader的几个核心方法:getExtension、getDefaultExtension、getAdaptiveExtension、getActivateExtension。其中后面三个方法最终都是调getExtension实现,前面介绍时,漏掉了对Wrapper代理类的处理,特做此补充。加载SPI扩展类过程(loadClasses方法)中,会缓存代理类到cachedWrapperClasses,而代理类真正使用是在getExtension方法内部。我们知道,初次获取SPI实例时,会通过反射创建SPI实例并放入holder进行缓存,SPI实例创建过程中,会判断当前SPI接口是否有代理类(实现SPI接口,构造方法有且仅有一个参数,且参数是SPI接口),若有则会对当前SPI实例进行代理,并最中返回代理对象。也就是说,当前SPI接口有代理类的情况下getExtension最终返回的是一个代理对象(经过n层代理,其中n表示代理类个数)。
以Protocol为例,Protocol的SPI配置文件(仅展示代理类)内容如下:
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
那么,在对应的ExtensionLoader中,cachedWrapperClasses就会缓存ProtocolFilterWrapper、ProtocolListenerWrapper,最终getExtension返回的SPI实例就是经过ProtocolFilterWrapper、ProtocolListenerWrapper代理的代理对象,举个例子,执行Protocol.export()方法时,真正执行顺序如下:ProtocolListenerWrapper.export -> ProtocolFilterWrapper.export -> DubboProtocol.export(假设这里使用默认SPI扩展)。最后来看核心代码:
private T createExtension(String name) {
Class> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 先从缓存取,取不到则直接通过反射创建
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 注入依赖的SPI实例
injectExtension(instance);
Set> wrapperClasses = cachedWrapperClasses;
// 代理类核心逻辑,若当前SPI接口有代理类,则创建代理类,注意了,这里是n层代理(取决于代理类个数),最后调用的时候会层层调用代理方法;创建完代理类,同样注入代理类依赖的SPI实例,并最终返回代理类。
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
// 若有代理类,最终则会返回代理对象。
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
注:dubbo版本2.7.1,欢迎指正。