SPI的介绍请参考上篇博客:Dubbo SPI前篇——Java SPI源码及示例,本文主要介绍Dubbo的SPI实现(基于源码2.7.7
)。
1. Dubbo SPI 简介
Dubbo 并未使用 Java 原生的 SPI 机制,而是重新实现了一套功能更强的 SPI 机制,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法。
2. Dubbo SPI 示例
2.1 创建示例maven工程
2.2 编写SPI接口及实现(一般是外部使用人员扩展实现)
package com.qqxhb.spi.dubbo;
import org.apache.dubbo.common.extension.SPI;
@SPI("dog")//必须要加的注解,标识ݷ为Dubbo SPI,属性值用于指定默认的扩展点名称
public interface IAnimal {
void say();
}
package com.qqxhb.spi.dubbo;
public class Cat implements IAnimal {
@Override
public void say() {
System.out.println("猫叫========");
}
}
package com.qqxhb.spi.dubbo;
public class Dog implements IAnimal {
@Override
public void say() {
System.out.println("狗吠========");
}
}
2.3 编写接口配置文件
在classpath下创建META-INF/dubbo文件夹(也可以是META-INF/services和META-INF/dubbo/internal,当然还可以自定义实现,见下源码分析4),在文件夹下创建具体接口全限定名文件,在文件中配置接口的具体实现(类全限定名)。本例com.qqxhb.spi.dubbo.IAnimal文件内容如下:
cat=com.qqxhb.spi.dubbo.Cat
dog=com.qqxhb.spi.dubbo.Dog
2.4 使用ExtensionLoader加载实现类并调用
package com.qqxhb.spi.dubbo;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class DubboSPI {
public static void main(String[] args) {
// 根据扩展类型实例化 ExtensionLoader
ExtensionLoader<IAnimal> extensionLoader = ExtensionLoader.getExtensionLoader(IAnimal.class);
// 获取SPI注解中指定的默认扩展实现
extensionLoader.getDefaultExtension().say();
// 根据配置文件中的key获取对应的扩展
extensionLoader.getExtension("dog").say();
extensionLoader.getExtension("cat").say();
}
}
调用成功,结果在控制台打印:
狗吠========
狗吠========
猫叫========
3. Dubbo SPI 加载源码分析
- 根据示例代码可知,首先调用getExtensionLoader 方法获取一个 ExtensionLoader 实例,该方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。创建实例也就算对ExtensionFactory和type属性进行赋值。
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
@SuppressWarnings("unchecked")
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!");
}
//接口必须有@SPI注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//从ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS中取,取不到则创建一个新的实例
return (ExtensionLoader<T>) EXTENSION_LOADERS.computeIfAbsent(type, k -> new ExtensionLoader<T>(type));
}
//私有化构造函数
private ExtensionLoader(Class<?> type) {
this.type = type;
//通过SPI的方式获取ExtensionFactory的实例
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
- 有上面可以看到第一步只是获取了ExtensionLoader实例,并没SPI的主要逻辑,因此我们接着看org.apache.dubbo.common.extension.ExtensionLoader.getExtension(String)方法的源码
/**
* 根据名称获取扩展实例
* @param name
* @return
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
//名称必传,即配置文件中的key
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
//如果传true则会获取默认扩展,即SPI注解中指定的扩展
if ("true".equals(name)) {
return getDefaultExtension();
}
//根据名称获取实例持有对象(ConcurrentMap<String, Holder<Object>> cachedInstances),该对象内部就一个属性即具体的实例对象,
final Holder<Object> holder = getOrCreateHolder(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;
}
- 紧跟上一步源码,可以看到主要的逻辑在createExtension方法中,接下来就查看org.apache.dubbo.common.extension.ExtensionLoader.createExtension(String)方法源码
/**
* 创建 扩展实例
* @param name
* @return
*/
@SuppressWarnings("unchecked")
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, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
/**
* 下面是dubbo 的IOC和AOP实现原理
*/
//依赖注入(setter方法)
injectExtension(instance);
// 如果存在包装了则,循环进行包装类的注入
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//构造函数存在type类型的参数则认为是包装类,将上一个包装类作为实例注入到下一个包装类
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);
}
}
- 由上一步源码可知,在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。
/**
* 获取所有的扩展类
* @return
*/
private Map<String, Class<?>> getExtensionClasses() {
//从缓存中获取
Map<String, Class<?>> classes = cachedClasses.get();
//双重检测,获取不到则从配置文件加载
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
/**
* 加载扩展配置
* */
private Map<String, Class<?>> loadExtensionClasses() {
//缓存默认扩展名称即SPI注解指定的名称
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载指定文件夹下的配置文件,默认的不同策略及对应了不同的名称
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
}
return extensionClasses;
}
上面遍历的多个策略,实际就是多个配置文件路径,也可以自定义
//默认三个扩展配置路径
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 LoadingStrategy DUBBO_INTERNAL_STRATEGY = () -> DUBBO_INTERNAL_DIRECTORY;
private static LoadingStrategy DUBBO_STRATEGY = () -> DUBBO_DIRECTORY;
private static LoadingStrategy SERVICES_STRATEGY = () -> SERVICES_DIRECTORY;
private static LoadingStrategy[] strategies = new LoadingStrategy[] { DUBBO_INTERNAL_STRATEGY, DUBBO_STRATEGY, SERVICES_STRATEGY };
/**
* 可以自定义实现
* @param strategies
*/
public static void setLoadingStrategies(LoadingStrategy... strategies) {
ExtensionLoader.strategies = strategies;
}
- 根据加载扩展的源码可知,扩展实现是通过loadDirectory方法加载的,因此接下来看此方法的源码
org.apache.dubbo.common.extension.ExtensionLoader.loadDirectory(Map<String, Class<?>>, String, String, boolean, String...)
/**
* 加载指定路径下的扩展实现
* @param extensionClasses
* @param dir
* @param type
* @param extensionLoaderClassLoaderFirst
* @param excludedPackages
*/
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
//文件名称 路径+type(接口全限定名)
String fileName = dir + type;
try {
//所有的资源都封装成URL
Enumeration<java.net.URL> urls = null;
//获取线程上下文类加载器,如果没有则使用加载ExtensionLoader的类加载器
ClassLoader classLoader = findClassLoader();
/*
* 通过getResources加载所有的同名文件资源
*/
// 首先尝试从扩展加载程序的类加载程序加载,
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
//扩展没有加载则使用上下文加载器加载
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//根据URL加载类
loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
6.接着上面的源码,分析loadResource方法的源码
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, String... excludedPackages) {
try {
//按行加载配置文件内容
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
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 && !isExcluded(line, excludedPackages)) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
- loadResource 源码首先就是按行读取,接着调用loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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.");
}
// 检测目标类上是否有 Adaptive 注解,有则缓存到AdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
//判断是不是包装类(看是否存在以当前type(接口)未参数的构造函数),是则缓存到包装类中
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
//如果没哟设置名称,则获取Extension注解值作为名称,若也没有,则使用类名小写做为名称
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);
}
}
//根据"\\s*[,]+\\s*"切分名称
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
// 存储 name 到 Activate 注解对象的映射关系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 存储 Class 到名称的映射关系
cacheName(clazz, n);
//名称到Class的映射
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
到这里SPI核心的源码就分结束了,有一些小的细节可以自行查看dubbo源码中的common模块:https://github.com/qqxhb/dubbo/tree/master/dubbo-common
示例源码:https://github.com/qqxhb/dubbo-demo/tree/master/dubbo-source-code-demo
下篇相关博客:一文彻底理解Dubbo SPI 自适应(Adaptive)拓展原理