Dubbo的SPI机制
Posted 源码王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo的SPI机制相关的知识,希望对你有一定的参考价值。
插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议、注册中心的注册方式等等。具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单,满足不了一些复杂的需求,所以dubbo重写了一套SPI机制,实现了类似spring的IOC和AOP的机制,灵活度和扩展性大大得以提升,这套机制很有用,我们如果能深刻理解,完成可以把它从dubbo的代码中剥离出来,用在自己系统中。这篇文章先简单讲一下这套机制:
在dubbo的jar内部的/META-INF/dubbo/internal下会有许多以接口名命名的文件,如图:
这些文件正是用于扩展用途的,我们称之为SPI描述文件。每个文件就代表一种扩展,文件打开后,在文件中会以行为单位配置该扩展接口的具体实现类,每个扩展可以有多个实现,以key-value的形式进行配置。例如打开/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件,其中的每一行都是com.alibaba.dubbo.rpc.Protoco接口的一个实现,内容如下图:
在使用时,Dubbo提供了两个类,一个是com.alibaba.dubbo.common.extension包下的ExtensionLoader类,另一个是com.alibaba.dubbo.common.extension包下的ExtensionFactory,使用的代码如下:
例如我们想通过插件机制获取某个接口扩展类,以com.alibaba.dubbo.rpc.Protocol为例:
@Test public void getExtensionLoader(){ ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class) ; Protocol protocol = (Protocol) extensionLoader.getExtension("dubbo") ; }
我们对上面的单元测试做个简单分析,我们想获取某个SPI接口实例其实就两步:
1. 获取相应的SPI接口的ExtensionLoader实例
2. 通过ExtensionLoader实例获取相应的SPI接口实例
而要明白原理,则需要搞清以下4个问题就可以
1. 每个Dubbo的SPI接口上都要有SPI的类注解
2. 每个Dubbo的SPI接口的具体实例,可通过调用其所属的ExtensionLoader实例的getExtension方法来获取:
3. 每个Dubbo的SPI接口的ExtensionLoader实例又是如何产生的呢
4. 每个ExtensionLoader实例中的实例属性objectFactory(类型为ExtensionFactory)的作用
以下摘抄出相应的代码来问答上述问题,代码是最好的答案:
代码的执行调用顺序:
getExtension方法——>createExtension方法——>getExtensionClasses方法——>loadExtensionClasses方法——>loadFile方法
//这段代码用来回答上述第2个问题 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; }
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, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); //此处将要完成注入工作,这跟Spring的Bean创建的过程类似,要完成一些依赖注入工作,在后面也能看到ExtensionFactory的作用
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
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 + ") could not be instantiated: " + t.getMessage(), t);
}
}
// getExtensionClasses方法的实质性代码。 private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { String value = defaultAnnotation.value(); if(value != null && (value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if(names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if(names.length == 1) cachedDefaultName = names[0]; } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
//这段代码回答上述第3个问题
//ExtensionLoader的构造方法 private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
//此代码说明 ExtensionFactory 的作用,即上述第4个问题
private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
总结:本文只是对Dubbo的SPI机制做一个粗的说明,其实一句话就能说清楚,动态获取SPI接口的实例 ,最简单的方式就是调用 ExtensionLoader实例 的 T getExtension(String name)方法就可以,传入的参数就是上述SPI描述文件中的key ,而 ExtensionFactory 的作用就是为了完成SPI实例间的依赖注入。后面我们还会针对Dubbo的SPI机制做更深入的分析。
以上是关于Dubbo的SPI机制的主要内容,如果未能解决你的问题,请参考以下文章
来撸一撸Dubbo之SPI机制源码,SPI到底解决了什么问题?