Dubbo的扩展机制是基于Dubbo SPI来是实现的,可以说SPI是Dubbo扩展机制的核心,那么想读懂Dubbo的源码,首先读懂Dubbo SPI源码是一个非常不错的选择。
什么是SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
示例
package org.apache.dubbo.demo.provider.javaspi;
public interface JavaSpi {
void say(String str);
}
package org.apache.dubbo.demo.provider.javaspi;
public class DemoOneJavaSpi implements JavaSpi{
@Override
public void say(String str) {
System.out.println("one:" + str);
}
}
package org.apache.dubbo.demo.provider.javaspi;
public class DemoTwoJavaSpi implements JavaSpi{
@Override
public void say(String str) {
System.out.println("two:" + str);
}
}
文件配置路径:META-INF/services(此路径是固定的),文件名:org.apache.dubbo.demo.provider.javaspi.JavaSpi
org.apache.dubbo.demo.provider.javaspi.DemoOneJavaSpi
org.apache.dubbo.demo.provider.javaspi.DemoTwoJavaSpi
package org.apache.dubbo.demo.provider.javaspi;
import org.junit.jupiter.api.Test;
import java.util.ServiceLoader;
public class JavaSpiTest {
@Test
public void test1() {
ServiceLoader<JavaSpi> load = ServiceLoader.load(JavaSpi.class);
load.forEach(item -> item.say("demo"));
}
}
Dubbo SPI
Dubbo并未使用Java原生的 SPI 机制,而是对其进行了增强,在满足了API扩展的前提下,提供了依赖注入的功能。与Java SPI不同的是,Dubbo SPI的配置文件是放在META-INF/dubbo目录下,其配置采用key-value的方式,且需扩展的接口需标注@SPI注解。
示例
package org.apache.dubbo.demo.provider.dubbospi;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface DubboSpi {
void say(String str);
}
文件配置路径:META-INF/dubbo(此路径是固定的),文件名:org.apache.dubbo.demo.provider.javaspi.JavaSpi
one=org.apache.dubbo.demo.provider.dubbospi.DemoOneDubboSpi
two=org.apache.dubbo.demo.provider.dubbospi.DemoTwoDubboSpi
package org.apache.dubbo.demo.provider.dubbospi;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;
import java.util.ServiceLoader;
public class DubboSpiTest {
@Test
public void test2() {
ExtensionLoader<JavaSpi> extensionLoader =
ExtensionLoader.getExtensionLoader(DubboSpi.class);
DubboSpi one = extensionLoader.getExtension("one");
one.say("test one");
JavaSpi two = extensionLoader.getExtension("two");
two.say("test two");
}
}
由上述例子可以大概知道,Dubbo SPI是封装在ExtensionLoader中实现的,是通过ExtensionLoader.getExtensionLoader(Class tClass)方法来获取其扩展的。
源码分析
获取扩展加载器
public class ExtensionLoader<T> {
/**
* 加载dubbo spi配置,type必须为interface,且必须有{@link SPI}标记
*/
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// 非interface抛出参数非法
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缓存,不存在则创建,并添加到缓存
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;
}
/**
* 构造函数
*/
private ExtensionLoader(Class<?> type) {
// 需要扩展的接口类
this.type = type;
// 扩展类工厂
// 此处为获取自适应扩展,在后面将Dubbo SPI自适应扩展机制时再
// 如果为ExtensionFactory则为null,否则先获取到ExtensionFactory的扩展类加载器ExtensionLoader<ExtensionFactory>
// 再通过ExtensionLoader<ExtensionFactory>的getAdaptiveExtension方法获取到具体的ExtensionFactory
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
}
上面为获取扩展类加载器的源码解析,没有太复杂的地方,按照流程走下来,无非就是先获取缓存中的ExtensionLoader,不存在则创建并加入缓存。值得注意的是构造函数中为objectFactory赋值的操作,此处为获取自适应扩展,此处先不做分析,待后面分析Dubbo SPI自适应扩展机制时再进行详解。接下里继续分析Dubbo SPI,其入口方法为getExtension(String name)
获取扩展类实例
/**
* 查找具有给定名称的扩展名。如果找不到指定的名称,则将引发{@link IllegalStateException}
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
return getExtension(name, true);
}
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的扩展实现类,默认的扩展由@SPI注解指定,如果未配置,则为null
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中
holder.set(instance);
}
}
}
return (T) instance;
}
getExtension方法内部有两种获取实例的方式,第一种是获取默认的实例,其名称由作用在扩展类上的@SPI注解指定,第二种是根据扩展类名称获取实例,第一种回回归到第二种,所以我们接下来继续分析第二种获取扩展实例的源码。从上面可以看到,第二种获取扩展实例的方式中有双重检验,先获取缓存,缓存不存在则创建。值得注意的是这里的双重校验及加锁操作,为啥锁instance对象能保证线程安全呢?我们可以进入getOrCreateHolder这个方法,虽然这里也没有加锁,但是它使用的是ConcurrentMap,这个map是线程安全的,从而保证了线程的安全。
创建扩展实例
/**
* 根据名称创建扩展类
* @param name 扩展类名称
* @param wrap 是否需要使用包装类
* @return T
*/
@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
// 获取扩展类
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);
}
// 属性注入
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)) {
// 这个方法看似简单,就是简单的遍历,实际上在存在多个包装类的情况下,会将上一次循环获取到的包装类通过构造方法注入到新的包装类
// 这样就保证了在存在多个包装类的情况下,多个包装类都可以可行,值得一提的是,可以对不同的包装类指定执行顺序
// TODO 如果实现包装类的排序待定
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
// 如果扩展类使用类@Wrapper标注,表示该类不要使用包装类
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
//
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
// 初始化扩展类,主要是判断该扩展类是否实现类Lifecycle,如果实现了则执行该类的initialize()方法
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
从createExtension方法的方法体中可以看出,这个方法的逻辑显得还是挺复杂的,逻辑顺序为获取扩展类、实例化扩展类、IOC注入、使用代理、初始化扩展类,这几个顺序来进行详细的分析。
获取扩展类
/**
* 获取扩展类,此处有双重检查
*/
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;
}
getExtensionClasses方法的代码不难理解,缓存不存在就加载扩展类,并添加到缓存,接下来分析loadExtensionClasses方法。
/**
* 加载扩展类,在getExtensionClasses中同步
*/
private Map<String, Class<?>> loadExtensionClasses() {
// 从@SPI中提取并缓存默认扩展名
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 遍历加载策略,加载Dubbo SPI配置文件
for (LoadingStrategy strategy : strategies) {
// 此处到了加载并解析Dubbo SPI配置文件的方法
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
// 兼容的老版本,阿里团队将Dubbo捐献给Apach后,包名做了改动
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
loadExtensionClasses方法做了两个动作,第一个动作是解析扩展类上的@SPI注解,并获取其默认值,默认值不为空的话,则设置为默认的扩展类的实现类的名称,并添加到缓存;第二个是遍历Dubbo SPI的加载策略,通过loadDirectory方法加载并解析Dubbo SPI的配置文件。
/**
* 加载并解析Dubbo SPI配置
* @param extensionClasses 扩展类
* @param dir 需要加载的包名
* @param type 需要加载类
* @param extensionLoaderClassLoaderFirst 扩展加载器类加载器优先
* @param overridden 覆盖
* @param excludedPackages 排除的包
*/
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
// 要加载的文件名 格式如下
// META-INF/services/org.apache.dubbo.common.context.FrameworkExt
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
// 获取类加载器
ClassLoader classLoader = findClassLoader();
// 尝试先从ExtensionLoader的ClassLoader加载
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();
// 加载resources
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadDirectory方法按流程下来先是解析完整的配置文件路径,获取ClassLoader,然后通过ClassLoader获取所有资源链接,最后通过loadResource加载资源。
/**
* 加载并解析Dubbo SPI配置
* @param extensionClasses 扩展类
* @param classLoader 类加载器
* @param resourceURL SPI资源文件
* @param overridden 是否覆盖
* @param excludedPackages 排除的包
*/
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;
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) {
// 获取Dubbo SPI配置文件的key
name = line.substring(0, i).trim();
// 获取Dubbo SPI配置文件的value,如one=org.apache.dubbo.demo.provider.javaspi.DemoOneDubboSpi
line = line.substring(i + 1).trim();
}
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
// 加载类
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
}
} 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方法只做了两件事,首先是读取资源信息,按照Dubbo SPI配置文件的约定,解析出每一行的扩展类实现的名称及类名,最后调用loadClass方法加载对应的扩展类。
/**
*
* @param extensionClasses 扩展类
* @param resourceURL 资源文件
* @param clazz 需要加载的类
* @param name 需要加载类的名称
* @param overridden 是否覆盖
*/
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.");
}
// 检测目标类上是否有@Adaptive注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 设置缓存
cacheAdaptiveClass(clazz, overridden);
// 检测类是否是wrapper类型
} else if (isWrapperClass(clazz)) {
// 设置缓存
cacheWrapperClass(clazz);
// 到了这里表示这是一个普通的扩展类
} else {
// 校验该类是否有默认的构造方法,没有则抛出异常
clazz.getConstructor();
// 如果name为null,则尝试从Extension注解中获取name,或使用小写的类名作为name
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);
}
}
// 切分name
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, overridden);
}
}
}
}
loadClass方法分为了三种情况来加载扩展类:扩展类是否有@Adaptive注解、是否为包装类、一个普通的扩展类,这里没有太复杂的逻辑,无非就是操作各种缓存如cacheAdaptiveClass和cacheWrapperClasses、cacheNames等,值得注意的是前两种情况,现在来看的话,我们并不知道这样做的目的是啥,其实第一种情况是用于Dubbo SPI的自适应扩展机制,这一部分我们将在Dubbo SPi自适应扩展中有详解,不管是第一种还是第二种,在Wrapper和Adaptive详解中有讲解其作用、使用方式、原理。
至此,获取扩展类阶段结束,我们再回到获取扩展类实例往下看,在getExtensionClasses方法执行完毕后,就是通过反射实例化该扩展类,然后通过injectExtension方法对实例进行以来注入
Dubbo IOC
/**
* IOC,基于setter方法来实现
* @param instance 扩展类实例
*/
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
// 遍历该实例的方法
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
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) {
// 注入
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;
}
如上,Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。其中不需要注入的依赖,可用@DisableInject注解作用在该setter方法上。
我们再回到创建扩展类实例可以看到,在结束了IOC注入后,将执行是否使用包装类的逻辑,在后面的Dubbo源码篇---Wrapper详解中将会详细的分析。
最后对Dubbo SPI的加载流程做个总结:
- 通过ExtensionLoader.getExtensionLoader(Class clazz)方法获取到扩展类加载器
- 通过ExtentsionLoader中的getExtensionClass()方法对Dubbo SPI配置文件进行解析并加入缓存(包括不限于名称、扩展类)
- 通过getExtension(String name)实例化扩展类并加入缓存
- 对已实例化的扩展类进行IOC注入,基于setter方式
- 返回扩展类实例