一、JDK的SPI思想
SPI 的全名为 Service Provider Interface,在面向对象的设计里面,模块之间推荐基于接口编程,而不是实现类进行硬编码,这样做也是为了模块设计的可插拔原则。为了在模块装配的时候不在程序里指明是哪个实现,就需要一种服务发现的机制,JDK 的 SPI 就是为某个接口寻找服务实现的。JDK 提供了服务实现查找的工具类:java.util.ServiceLoader,它会去加载 META-INF/service/目录下的配置文件。
二、Dubbo的SPI扩展机制原理
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
-
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
-
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
-
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
在扩展类的 jar 包内,放置扩展点配置文件 META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
以扩展 Dubbo 的协议为例,在协议的实现 jar 包内放置文本文件:META-INF/dubbo/org.apache.dubbo.rpc.Protocol,内容为:
xxx=com.alibaba.xxx.XxxProtocol
实现类内容:
package com.alibaba.xxx;
import org.apache.dubbo.rpc.Protocol;
public class XxxProtocol implements Protocol {
// ...
}
那么,Dubbo 怎么在运行期去判断要加载哪个具体的实例?
三、Dubbo的SPI扩展机制实现
1) 相关注解
-
@SPI 注解
在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。 -
@Adaptive 注解
为了保证 Dubbo 在内部调用具体实现的时候不是硬编码来指定引用哪个实现,也就是为了适配一个接口的多种实现,这样做符合模块接口设计的可插拔原则,也增加了整个框架的灵活性,该注解也实现了扩展点自动装配的特性。 -
@Activate 注解
扩展点自动激活的注解,就是用条件来控制该扩展点实现是否被自动激活加载,在扩展实现类上面使用,实现了扩展点自动激活的特性,它可以设置两个参数,分别是 group 和 value。
2) 动态生成适配器类
参考官方文档中给出的类的大致结构如下:
package <扩展点接口所在包>;
public class <扩展点接口名>$Adpative implements <扩展点接口> {
public <有@Adaptive注解的接口方法>(<方法参数>) {
if(是否有URL类型方法参数?) 使用该URL参数
else if(是否有方法类型上有URL属性) 使用该URL属性
# <else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败!>
if(获取的URL == null) {
throw new IllegalArgumentException("url == null");
}
根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。
如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension");
在扩展点实现调用该方法,并返回结果。
}
public <有@Adaptive注解的接口方法>(<方法参数>) {
throw new UnsupportedOperationException("is not adaptive method!");
}
}
我们以 Protocol 为例来看下生成的扩展点类的结构:
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.apache.dubbo.rpc.Protocol {
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.apache.dubbo.rpc.Protocol.getDefaultPort() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public com.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.apache.dubbo.common.URL arg1) throws com.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.apache.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException("Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.apache.dubbo.rpc.Exporter export (com.apache.dubbo.rpc.Invoker arg0) throws com.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException("Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.apache.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.apache.dubbo.rpc.Protocol.destroy() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}
关于上述生成的代码,我们主要注意如下几个问题:
-
所有未使用 @Adaptive 注解标注的接口方法,默认都会抛出异常;
-
使用 @Adaptive 注解标注的方法中,其参数中必须有一个参数类型为 URL,或者其某个参数提供了某个方法,该方法可以返回一个 URL 对象;
-
在方法的实现中会通过 URL 对象获取某个参数对应的参数值,如果在接口的 @SPI 注解中指定了默认值,那么在使用 URL 对象获取参数值时,如果没有取到,就会使用该默认值;
-
最后根据获取到的参数值,在 ExtensionLoader 中获取该参数值对应的服务提供类对象,然后将真正的调用委托给该服务提供类对象进行处理;
-
在通过 URL 对象获取参数时,参数key 获取的对应规则是,首先会从 @Adaptive 注解的参数值中获取,如果该注解没有指定参数名,那么就会默认将目标接口的类名转换为 点分形式 作为参数名。
根据上面的分析,Dubbo Adaptive 的实现机制步骤已经比较清晰了,主要分为如下三个步骤:
-
加载标注有 @Adaptive 注解的接口,如果不存在,则不支持 Adaptive 机制;
-
为目标接口按照一定的模板生成子类代码,并且编译生成的代码,然后通过反射生成该类的对象;
-
结合生成的对象实例,通过传入的 URL 对象,获取指定key 的配置,然后加载该key 对应的类对象,最终将调用委托给该类对象进行处理;
可以看到,通过这种方式,Dubbo 就实现了一种通过配置参数动态选择所使用的服务 的目的,而实现这种机制的入口主要在 ExtensionLoader.getAdaptiveExtension()方法,查看如下的具体实现:
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
//type必须是接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
//必须注解@SPI
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//缓存中没有,则新建ExtensionLoader对象放入缓存
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
public T getAdaptiveExtension() {
//从缓存中获取,每个SPI接口至多只有一个 Adaptive 适配对象
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//查找并创建Adaptive代理类实例
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t; //记录创建自适应扩展点错误信息
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
//获取自适应扩展点实例,并注入依赖实例
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
//获取扩展点Class
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//创建适配扩展点Class并返回
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
rivate Class<?> createAdaptiveExtensionClass() {
//拼接自适应扩展点 class 的字符串
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
//获取编译器
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader); //编译字符串为 class
}
这里 createAdaptiveExtensionClassCode 方法生成自适应扩展点类的流程,我们就不具体的进行分析,就是一些字符串的拼接操作。
3) 加载具体扩展实例
通过上面的示例可以看到,Dubbo 在动态生成的适配类中想获取具体的扩展实例,都会调用 getExtension 方法,那么我们来看看在调用 getExtension 方法获取具体实例过程中会做了哪些操作。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
//获取或创建扩展点的辅助实例
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//根据扩展名,获取对应的 class,生成扩展实例
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name) {
//获取扩展名对应的类型Class
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);
}
//为扩展实例,注入setXXX方法对应的属性值
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//Wrapper不为空,将当前实例包装返回
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);
}
}
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
//遍历扩展实例的所有方法
for (Method method : instance.getClass().getMethods()) {
//跳过非 setXXX 的方法
if (!isSetter(method)) {
continue;
}
//跳过 @DisableInject 注解的方法
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//截取方法的属性名
String property = getSetterProperty(method);
//从对象工厂获取对应属性名的实例
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//反射,调用setXXX方法设置属性值
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
这里运用到了两个扩展点的特性,分别是自动装配(IoC)和自动包装(AOP)。
对于自动装配,这类似与Spring 的属性依赖自动注入,Dubbo 提供了两中扩展工厂实例,对于@SPI注解的扩展实例依赖,从扩展加载器类的缓存中获取自适配扩展实例注入。
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
public class SpringExtensionFactory implements ExtensionFactory {
//省略部分代码...
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
//SPI should be get from SpiExtensionFactory
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
for (ApplicationContext context : CONTEXTS) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
if (Object.class == type) {
return null;
}
for (ApplicationContext context : CONTEXTS) {
try {
return context.getBean(type);
} catch (NoUniqueBeanDefinitionException multiBeanExe) {
logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
} catch (NoSuchBeanDefinitionException noBeanExe) {
if (logger.isDebugEnabled()) {
logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
return null;
}
}
对于Spring 的自动装配,Dubbo 会从Spring 的IoC容器(ApplicationContext)中获取注入,该容器实例会在服务暴露、引用的时候被设置到SpringExtensionFactory。
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
//省略代码...
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
}
}
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware {
//省略代码...
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
supportedApplicationListener = addApplicationListener(applicationContext, this);
}
关于Wrapper对象,这里需要说明的是,其主要作用是为目标对象实现AOP。Wrapper对象有两个特点:
- 与目标对象实现了同一个接口;
- 有一个以目标接口为参数类型的构造函数;
这也就是上述createExtension()方法最后封装Wrapper对象时传入的构造函数实例始终可以为instance实例的原因。
四、总结
本文首先对 JDK的SPI 和 Dubbo的SPI 进行了简单的对比,说明了Dubbo相对于 JDK的SPI 所提供的额外的动态配置性能,然后通过一个示例来展示了 Dubbo的SPI 的使用方式,最后着重讲解了用于实现 Dubbo SPI的ExtensionLoader的实现原理。
引用