Dubbo的灵活性体现在每个系统功能点都可以动态扩展为新的实现,而且只需要额外配置,不需要修改dubbo源码,非常符合面向对象设计的开闭原则,其实现原理利用了JDK5.0的自动发现机制,具体使用相关代码是ExtensionLoader
本文目的:对Dubbo使用ExtensionLoader动态加载扩展点相关源码实现介绍
面向读者:要求对dubbo的扩展点ExtensionLoader使用有基本的了解
背景介绍:官网链接-扩展点加载
- 扩展点配置
- 扩展点自适应
示例代码:来源于Dubbo源码,Git链接
相关术语:
扩展点 Extension - 可以理解为扩展某功能接口的实现类
关于扩展点配置
Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。具体在Dubbo应用实现如下。
服务加载配置文件位于下面目录:
代码能找到配置文件,是由下面相关代码定义的:
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
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/";
如何根据扩展名加载对应实现类
具体例子:下面就是根据扩展名“mymock”加载对应类MockFilter:
MockFilter filter = (MockFilter)ExtensionLoader.getExtensionLoader(Filter.class).getExtension("mymock");
“mymock”就是指定名字,定义在配置文件内:
mymock=com.alibaba.dubbo.config.spring.filter.MockFilter
其中这里的约定:MockFilter是接口Filter的实现类,文件名
com.alibaba.dubbo.rpc.Filter意思是接口类Filter在包com.alibaba.dubbo.rpc下
目录结构:
Dubbo注解@Adaptive用法
-加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader在会自动注入依赖的扩展点。
-ExtensionLoader通过扫描扩展点实现类的所有set方法来判定其成员。
即ExtensionLoader会执行扩展点的拼装操作。
-ExtensionLoader注入的依赖扩展点是一个Adaptive实例,直到扩展点方法执行时才决定调用是一个扩展点实现。
1)Adaptive注解定义:
...
public @interface Adaptive {
...
String[] value() default {};
}
2)接口例子
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
...
...
}
这些注解在代码中如何发挥作用?
ExtensionLoader的createAdaptiveExtensionClassCode()方法根据注解@Adaptive的key值注入对应实现类。
● 首先知道ExtensionLoader注入的依赖扩展点是一个Adaptive实例,直到扩展点方法执行时才决定调用是哪一个扩展点实现。
● Dubbo使用URL对象(包含了Key-Value)传递配置信息。key是在配置xml里的定义使用,例如xml指定server = value,则key是‘server’, 然后Adaptive实例代码中指定加载的扩展名extName为value,根据此加载对应实现类。
几点注意:
1)Adaptive实例的代码是由createAdaptiveExtensionClassCode()方法动态产生的。
2)”直到扩展点方法执行时才决定调用是哪一个扩展点实现”, 这句话理解为Adaptive代码中会根据extName扩展名指定加载对应实现类。
3)URL封装的信息里可能含有从配置文件xml传递过来的key=value信息,假如没有,则extName取接口类的注解@SPI(“netty”)中指定的名字,例如这里是extName为Netty,这段逻辑代码如下图部分片段:
java.lang.reflect.Method 对象反射获取注解:
...
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
...
...
String[] value = adaptiveAnnotation.value();
...
上面代码片段相关逻辑在Adaptive注解中也有如何使用,具体以代码说明为准。
下图附上protocol的代码生成Adaptive实例供参考:
extName的值就是根据上面逻辑生成的
(对于Protocol扩展的使用,URL传给Protocol扩展点,基于扩展点的Adaptive机制,根据URL的协议头,进行不同协议的服务暴露或引用)
拓展延伸
类似地,Wrapper包装类是用Wrapper.makeWrapper方法动态生成新包装类的。其它类型的注解使用也是在代码层面利用Class反射原理获取注解及其值后生成代码。
启发与收获
Dubbo的设计有很多值得借鉴学习的地方,本文希望能够让你获得启发。
1. 开闭原则的面向对象设计,对于变化点的可扩展性,利用配置式服务发现机制动态加载相关接口实现类。
2. 代码动态生成,动态编译技术。
3. 平时开源框架例如Spring,那些系统注解或者自定义注解的实现原理。