我们都是知道一个合格的开源框架对于扩展的支持都要是相当弹性的,Dubbo 也不例外。Dubbo采用微内核+插件体系,使得设计优雅,扩展性强。Dubbo的扩展机制是基于SPI思想来实现的,但是并没有采用JDK中原生的SPI机制。
java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。
Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等。
微内核架构 (Microkernel architecture) 模式也被称为插件架构 (Plugin architecture) 模式。原本与内核集成在一起的组件会被分离出来,内核提供了特定的接口使得这些组件可以灵活的接入,这些组件在内核的管理下工作,但是这些组件可以独立的发展、更改(不会对现有系统造成改动),只要符合内核的接口即可。典型的例子比如,Eclipse,IDEA 。
Dubbo内核对外暴露出扩展点,通过扩展点可以实现定制的符合自己业务需求的功能。Dubbo内核通过ExtensionLoader扩展点加载器来加载各个SPI扩展点。Dubbo 内核对扩展是无感的 ,完全不知道扩展的存在 ,内核代码中不会出现使用具体扩展的硬编码。
术语说明 :
SPI : Service Provider Interface 。
扩展点 : 称 Dubbo 中被 @SPI 注解的 Interface 为一个扩展点。
扩展 : 被 @SPI 注解的 Interface 的实现称为这个扩展点的一个扩展。
扩展点约定 : 扩展点必须是 Interface 类型 ,必须被 @SPI 注解 ,满足这两点才是一个扩展点。
扩展定义约定:在 META-INF/services/$扩展点接口的全类名,META-INF/dubbo/$扩展点接口的全类名 , META-INF/dubbo/internal/$扩展点接口的全类名 , 这些路径下定义的文件名称为 $扩展点接口的全类名 , 文件中以键值对的方式配置扩展点的扩展实现。
默认适应扩展 :被 @SPI("abc") 注解的 Interface , 那么这个扩展点的缺省适应扩展就是 SPI 配置文件中 key 为 "abc" 的扩展。
通过注解@SPI("dubbo"),默认使用Protocol的实现类DubboProtocol,其实dubbo和实现类DubboProtocol关系类似Spring配置文件中的id和class的关系,不同的是Dubbo的关系是
配置在目录/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中,文件内容为:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
简单来说其实现机制就类似Spring的bean注入,通过key(dubbo、http、hessian)来找到其实现类。
扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。这个类的源码是很有必要深入学习的。从 Dubbo 内核设计简图可以看到,现在的学习还没有接触到 dubbo 的内核。
public class ExtensionLoader {
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/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap, ExtensionLoader>>();
private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>();
// ==============================
private final Class> type;
private final ExtensionFactory objectFactory;
private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>();
private final Holder
ExtensionLoader 中会存储两个静态属性 , EXTENSION_LOADERS 保存了内核开放的扩展点对应的 ExtensionLoader 实例对象 (说明了一种扩展点有一个对应的 ExtensionLoader 对象)。EXTENSION_INSTANCES 保存了扩展类型 (Class) 和扩展类型的实例对象。
type : 被 @SPI 注解的 Interface , 也就是扩展点。
objectFactory : 扩展工厂,可以从中获取到扩展类型实例对象 ,缺省为 AdaptiveExtensionFactory。
cachedNames : 保存不满足装饰模式(不存在只有一个参数,并且参数是扩展点类型实例对象的构造函数)的扩展的名称。
cachedClasses : 保存不满足装饰模式的扩展的 Class 实例 , 扩展的名称作为 key , Class 实例作为 value。
cachedActivates : 保存不满足装饰模式 , 被 @Activate 注解的扩展的 Class 实例。
cachedAdaptiveClass : 被 @Adpative 注解的扩展的 Class 实例 。
cachedInstances : 保存扩展的名称和实例对象 , 扩展名称为 key , 扩展实例为 value。
cachedDefaultName : 扩展点上 @SPI 注解指定的缺省适配扩展。
createAdaptiveInstanceError : 创建适配扩展实例过程中抛出的异常。
cachedWrapperClasses : 满足装饰模式的扩展的 Class 实例。
exceptions : 保存在加载扩展点配置文件时,加载扩展点过程中抛出的异常 , key 是当前读取的扩展点配置文件的一行 , value 是抛出的异常
我们已经知道Dubbo将Protocol的实现类配置到/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol配置文件中,接下来我们要看的就是将配置文件中的对应关系解析出来了。其处理操作是在ExtensionLoder的loadFile方法中类来实现的,
简单来说读取dubbo=com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory,获取到键dubbo,初始化值com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory然后将对应关系保存到一个Map中,这样就可以根据key找到实现类了。
private void loadFile(Map> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class> clazz = Class.forName(line, true, classLoader);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else {
try {
clazz.getConstructor(type);
Set> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}