Dubbo中的SPI机制实现,自定义对接SPI
Posted Leo Han
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo中的SPI机制实现,自定义对接SPI相关的知识,希望对你有一定的参考价值。
SPI
在java中称为Service Provider Interface
,Dubbo中对java的spi机制自己实现相对应的逻辑。
我们以Dubbo中的Protociol为例,一般我们去获取对应的Protocol的时候都是通过如下方式:
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
// 或者
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
我们看下Dubbo中具体怎么实现,
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface())
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
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)
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
return loader;
可以看到,Dubbo中是每个通过SPI加载类型都有一个对应的ExtensionLoader
,我们看看具体怎么加载类的:
public T getExtension(String name)
T extension = getExtension(name, true);
if (extension == null)
throw new IllegalArgumentException("Not find extension: " + name);
return extension;
public T getExtension(String name, boolean wrap)
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)
instance = createExtension(name, wrap);
holder.set(instance);
return (T) instance;
private T createExtension(String name, boolean wrap)
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name))
throw findException(name);
try
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null)
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
injectExtension(instance);
if (wrap)
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null)
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
if (CollectionUtils.isNotEmpty(wrapperClassesList))
for (Class<?> wrapperClass : wrapperClassesList)
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name)))
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
initExtension(instance);
return instance;
catch (Throwable t)
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
private Map<String, Class<?>> loadExtensionClasses()
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies)
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
return extensionClasses;
这里我们首先会通过getExtensionClasses
去加载对应的SPI扩展类,通过loadExtensionClasses
去进行实际的加载,这里首先会用java的 ServiceLoader
去加载对应的LoadingStrategy
,而ServiceLoader
会加载当前classpath下META-INF.services
下对应去路径类对应的文件中列出的类:
而这里LoadingStrategy
则会加载如下三个实现类:
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy
而这里这几个LoadingStrategy
主要是定义了要扫描哪些文件夹:
DubboInternalLoadingStrategy
会加载如下文件夹:
public class DubboInternalLoadingStrategy implements LoadingStrategy
public String directory()
return "META-INF/dubbo/internal/";
public int getPriority()
return MAX_PRIORITY;
DubboLoadingStrategy
:
public class DubboLoadingStrategy implements LoadingStrategy
public String directory()
return "META-INF/dubbo/";
public boolean overridden()
return true;
public int getPriority()
return NORMAL_PRIORITY;
ServicesLoadingStrategy
:
public class ServicesLoadingStrategy implements LoadingStrategy
public String directory()
return "META-INF/services/";
public boolean overridden()
return true;
public int getPriority()
return MIN_PRIORITY;
也就是说,如果我们想要Dubbo的SPI区加载我们指定的目录下的实现的话,那么我们需要实现LoadingStrategy然后在classpath下新建预估ieorg.apache.dubbo.common.extension.LoadingStrategy名称的文件,文件的内容为我们自己实现的LoadingStrategy全名称,多个换行即可
从上面可以看到,Dubbo会从如下几个文件夹去加载对应的SPI扩展类:
META-INF/dubbo/internal/
、META-INF/dubbo/
、META-INF/services/
最终通过loadResource
去加载对应需要加载的类全名称文件里面的内容:
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages)
try
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8)))
String line;
String clazz = 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();
clazz = line.substring(i + 1).trim();
else
clazz = line;
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages))
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
catch (Throwable t)
catch (Throwable t)
可以看到,这里是按照行进行读取的,然后需要注意的是这里会判断每行是否有=,如果有=号的化,那么会进行分割,拿到对应的SPI需要加载的类
拿到类之后,接下来就去加载类,通过loadClass
实现:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException
if (!type.isAssignableFrom(clazz))
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
if (clazz.isAnnotationPresent(Adaptive.class))
cacheAdaptiveClass(clazz, overridden);
else if (isWrapperClass(clazz))
cacheWrapperClass(clazz);
else
clazz.getConstructor();
if (StringUtils.isEmpty(name))
name = findAnnotationName(clazz);
if (name.length() == 0)
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names))
cacheActivateClass(clazz, names[0]);
for (String n : names)
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
上面逻辑主要有三个:
- 加载的类上面是否有
Adaptive
注解,如果有Adaptive
那么会设置cacheAdaptiveClass
中 - 如果没有
Adaptive
注解,那么判断是否是Wrapper
类,这里Dubbo判断是否是Wrapper的方式很简单,就是判断这个类的构造函数是否支持只传入一个参数,且这个参数是待加载的类型
:
private boolean isWrapperClass(Class<?> clazz)
try
clazz.getConstructor(type);
return true;
catch (NoSuchMethodException e)
return false;
- 如果上面两项都不符合的话,如果name为空,那么查看类上
Extension
注解对应的值作为name,如果没有Extension
注解,那么name的逻辑为判断类名称的是否以待获取的类名称结尾,如果是直接直接取类名称去除待获取类名称的部分并转小写
然后将name和对应的class类型设置到缓存中去:
private String findAnnotationName(Class<?> clazz)
org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
if (extension != null)
return extension.value();
String name = clazz.getSimpleName();
if (name.endsWith(type.getSimpleName()))
name = name.substring(0, name.length() - type.getSimpleName().length());
return name.toLowerCase();
也就是说,我们通过
这样我们就加载到了所有的类,并设置到了对应的类中,如果不是Adaptive
和WrapperClass
那么会将类和对应name放置到一个map中并返回,然后在createExtension
中根据name
就能够获取到对应的类型
对于createExtension
,首先是根据获取到的类型进行实例化,并注入相关的属性(注入属性也是通过SPI机制,如果有set方法会进行注入,如果方法上有DisableInject
注解或忽略
实例化类之后,会进行wrap
也就是包装,会实例化Wrapper,然后将生成的实际类的实例注入进去,这里由于会存在多个Warapper类,会进行排序,通过获取Activate中的order属性
,这里是按照从小到大的顺序排列。
这样多个Wrapper实际上是一个Wraprer嵌套这一个Wrapper
。
所以说,Dubbo的SPI机制如果有warapper实现返回的是一个Wrapper类。
另外对于getAdaptiveExtension
,Dubbo中的处理也很简单暴力,直接加载对应要加载类名称后面加上$Adaptive
:
private Class<?> createAdaptiveExtensionClass()
ClassLoader classLoader = findClassLoader();
if (ApplicationModel.getEnvironment().getConfiguration(以上是关于Dubbo中的SPI机制实现,自定义对接SPI的主要内容,如果未能解决你的问题,请参考以下文章