04.dubbo扩展点实现
怎么入手分析与学习
- 可以看官方的ExtensionLoaderTest 测试用例,以这些具体的case作为入口。
- 也可以直接看ExtensionLoader代码。可以从getExtension,getAdaptiveExtension,getActivateExtension三个方法作为入口。
扩展点的使用
配置文件相关
配置文件放在哪里?
- META-INF/dubbo/internal/配置文件
- META-INF/dubbo/配置文件
- META-INF/services/配置文件
配置文件名是顶层接口全限定名,比如:com.alibaba.dubbo.rpc.Protocol
配置文件中内容:
一行是一个实现类的定义,大致是
beanName=实现类的class的全限定名,这个后面还可以接上#xxx(这个能力实际使用少)。beanName=这部分不是必须的。可以仅仅写实现类的全限定名。
注解相关
SPI
SPI注解用在顶层接口上,其值表示这个接口的默认实现类的beanName,也即是说指定一个顶层接口的默认实现通过SPI注解加载顶层接口上指定即可。
加了SPI注解的接口,我们就可以用扩展点机制结合配置文件的配置后,得到一个接口的具体实现,使用时如下:
@SPI("dubbo")
public interface Protocol {
// ...
}
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);
Adaptive
Adaptive注解用在顶层接口或者接口的方法上,其表示这个接口或者这个方法需要有adaptive的类委托完成,未加注解的会生成不支持的操作的方式实现。
我们以ProxyFactory为例子看下使用时具体做法。
首先,在对应的接口的方法上加了@Adaptive注解,表示这个方法需要adaptive。
@SPI("javassist")
public interface ProxyFactory {
@Adaptive({Constants.PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException;
@Adaptive({Constants.PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
其次,在使用的地方主要代码如下:
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
//...只列举相关代码
// 创建服务代理
return (T) proxyFactory.getProxy(invoker);
Activate
Activate注解用在实现类上,其表示实现类在rpc时根据url参数中以及注解中指定的key 目标value是否能匹配来决定此bean是否被选中(一般用在过滤器的命中判断上)。
扩展点实现
getExtension实现
大概逻辑是:先从已经创建的instance中查找,找不到则创建。
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension
创建分两到三步:
- 构造实例 ExtensionLoader.createExtension
- 为实例的字段进行注入 ExtensionLoader.injectExtension
- wrapper class的处理(未配置wrap的没有这一步骤)。cachedWrapperClasses是在扩展点load文件时就处理好了,ExtensionLoader的649行。wrap的处理就是将先扩展出来的实例作为下一个的构造函数和的参数传入。
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
这几步逻辑整体相对简单,主要靠反射完成,此处不再细细讲解代码了。
getAdaptiveExtension实现
扩展点对adaptive模式的支持,在整个dubbo中非常重要,使用广泛,如果这一步骤不能弄清楚的话,后面的代码阅读或有困扰,尤其在调试时,常常发现这个字段是adaptive后的,那个字段也是。
前文讲扩展点使用时,已经讲了,Adaptive模式支持两个级别: 类级别与方法级别。关于这一点我们可以看注解Adaptive的Target声明表示其支持TYPE和METHOD:
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
//......
}
dubbo自身代码中使用类级别的adaptive的不多,大概有AdaptiveCompiler和AdaptiveExtensionFactory。
不论是类级别的还是方法级别的adaptive,dubbo的扩展点机制都会为其生成一个扩展类。类级别的,一个类对应一个扩展类。方法级别的,一个类中的多个加了Adaptive注解的方法在一起生成一个扩展类。
代码入口处:ExtensionLoader.createAdaptiveExtension。
获取adaptive扩展实例的主要流程:
graph LR
A[getAdaptiveExtensionClass<br>创建适配class] -->B[newInstance<br>创建适配实例]
B --> C[injectExtension<br>注入相应字段]
getAdaptiveExtensionClass创建适配class的流程:
graph LR
A[createAdaptiveExtensionClassCode<br>生成适配class的代码] -->B[Compiler.compile<br>编译适配class的代码]
B -->C[javassist.CtClass.toClass<br>javassist编译出class]
通过上述两个流程图大概能明白扩展的适配实例的创建过程,就是通过生成代码的再编译出class再load上来后,再创建对应实例并注入字段的过程来完成的。
生成adaptive代码
代码在com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtensionClassCode()。
关于生成代码createAdaptiveExtensionClassCode的逻辑看两个生成的示例就大概明白了。
生成的代码可以在com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler.compile(String, ClassLoader)断点,观察其第一个参数的值。也可以修改createAdaptiveExtensionClassCode代码的逻辑,让其顺便生成下toString逻辑,在toString中把生成的代码吐出来。还有个办法就是利用arthas这个在线诊断工具的jad反编译命令去反编译运行期的class。当然你可以自己把生成的class的byte写入到一个文件后,将class的byte写文件可以通过CtClass的API writeFile来完成(dubbo默认采用Javassist的CtClass类编译),手动反编译,推荐工具procyon。
adaptive类代码示例
package com.alibaba.dubbo.registry;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString()
+ ") use keys([protocol])");
com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory) ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
return extension.getRegistry(arg0);
}
}
package com.alibaba.dubbo.rpc.cluster;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Cluster$Adaptive implements com.alibaba.dubbo.rpc.cluster.Cluster {
public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("cluster", "failover"); // 1. 根据adaptive配置的key到url(所有配置项的值都在url上体现)中**获取到这个key对应的值
if (extName == null)
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString()
+ ") use keys([cluster])");
com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster) ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);// 2. 以这个值作为扩展点的实现的名称去扩展点机制中获取到对应的bean
return extension.join(arg0); // 3.然后adaptive的方法由这个bean的同名方法来完成**
}
}
生成的代码要做的事情大致就是:
- 根据adaptive配置的key到url(所有配置项的值都在url上体现)中获取到这个key对应的值,
- 以这个值作为扩展点的实现的名称去扩展点机制中获取到对应的bean,
- 然后adaptive的方法由这个bean的同名方法来完成。至此,完成adaptive机制。
graph TD
A[1.从url中取配置key对应的配置值] -->B[根据配置值作为扩展实现的beanName]
B -->C[2.用扩展点机制根据beanName取到bean]
C -->D[3.让上步的bean的同名方法完成逻辑<br>达成adaptive目的]
编译adaptive代码
默认的主要逻辑在com.alibaba.dubbo.common.compiler.support.JavassistCompiler.doCompile(String, String)
这里的大致实例逻辑是,根据Javassist的API的要求告诉他package是什么,要import哪些类,类名时候什么,父类或者接口是什么,每个方法体是什么代码之类的处理。方法可以用CtNewMethod.make来完成。
getActivateExtension实现
具体实现在com.alibaba.dubbo.common.extension.ExtensionLoader.getActivateExtension(URL, String[], String)中。
该方法的主要罗就就是将该扩展点的所有激活的实例返回,其返回值是个列表。那么,什么叫激活?看com.alibaba.dubbo.common.extension.ExtensionLoader.isActive(Activate, URL)方法,就是根据url(这个是运行期形成的配置上下文)和Activate注解上配置的key,如果有这个key(或者以 .${key}结尾的)且key对应的值不为空则算激活。
同时,这个列表中的实例还需要排序,由com.alibaba.dubbo.common.extension.support.ActivateComparator.COMPARATOR排序器完成。具体逻辑是根据Activate的before、after和order配置值作为条件排序。
当然Activate的激活条件还支持Group,具体处理逻辑参见com.alibaba.dubbo.common.extension.ExtensionLoader.isMatchGroup(String, String[])。
总结梳理下dubbo扩展点有哪些能力
粗略的讲有:自动装配(注入)字段、代理、适配、包装、激活(一次获取多个命中实例)等。
- 能load class,这个class除了顶层接口class(在ExtensionLoader中对应type字段),还能load各实现类的class。
- 能创建instance。
- 能指定这个顶层接口的默认实现类的beanName。做法参见SPI注解部分。
- 能把创建出来的instance的字段注入。set开头的且有一个参数且是public的,注入。
- 能adaptive。根据url上对该接口配置的实现类,将该接口的事情交给这个实现类去做(我更多的理解成委托)。此能力采用代码生成再编译的方式。代码生成示例可以参见adaptive类代码示例。 adaptive只会生成一个adaptive实现类。生成代码的逻辑在com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtensionClassCode()
- 能wrapper。wrapper是指这个顶层接口的实现类的构造函数的入参是这个顶层接口类型。那么这个实现类称之为wrapper类,可以有多个,构建instance时不分多个之间的顺序。因为用来给构造函数传参的instance是这个顶层类的默认实现。比如com.alibaba.dubbo.rpc.Protocol接口,有实现类 com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol beanName是injvm,QosProtocolWrapper、ProtocolListenerWrapper、ProtocolFilterWrapper是其三个wrapper类。创建warpper instance的代码逻辑在com.alibaba.dubbo.common.extension.ExtensionLoader.createExtension(String)中。
- 能active。实现类加了Activate注解的。在ExtensionLoader.getActivateExtension时会根据当前的url(配置信息)中值来匹配Activate注解中指定的值是否能match,能match的表示是activate,意思是命中的。在过滤器扩展点中用到。比如这个过滤器是给CONSUMER group用。示例有ExceptionFilter等。同时该注解还能支持order属性来定义bean的顺序。
杂项
查找所有的dubbo扩展点形式的配置文件
find ./ -type f -name "com.alibaba.dubbo*"|grep -v "/target/"|grep -v "/bin/"|grep -v "/test/"
另:
关于代理模式,dubbo未实现通用的,只是rpc语义实现里rpc调用的代理,借助扩展点机器加动态代理完成。
具体其顶层接口是com.alibaba.dubbo.rpc.ProxyFactory。用在比如将EchoService编织进每次RPC调用中。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!