什么是SPI
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
了解过springboot的同学或许大多都认识一个文件:spring.factories
他存在的意义,就是为了告诉spring容器,某一个接口,存在哪些接口,他的哪些实现类是我们所需要的,动态替换接口的。
同样的,java也有一套SPI的机制。
java的SPI主要依靠的是 ServiceLoader ,而spring的SPI主要依靠的是 SpringFactoriesLoader ,而dubbo的SPI主要依靠的是 ExtensionLoader。
java的SPI我们这里就不另加描述了,可以看https://dubbo.apache.org/zh/docsv2.7/dev/source/dubbo-spi/
这里我们从ExtensionLoader切入dubbo的第一课源码。
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。
映入眼帘的,除了Logger之外,就是三个加载路径。跟java的SPI一样,是从规定路径下,读取类的全限定名作为加载的依据。加载的优先顺序从下至上。
代码语言:javascript复制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/";
接下来,就是一个默认加载默认扩展类名称的校验器(,左右无限个空格的正则表达式),加载扩展类的加载类的缓存map,和扩展类的缓存map。跟spring的三级缓存有些类似。通过EXTENSION_LOADERS的value(loader)获取到EXTENSION_INSTANCES里的对象(实际的扩展类)。
代码语言:javascript复制private static final Pattern NAME_SEPARATOR = Pattern.compile("\s*[,] \s*");
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
以上就是这个类的所有静态常量类。
在进入接下来的解析之前我们不妨看一个简单的demo:
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。
在上面涉及到的三个加载路径下任意一个路径下配置文件,文件名称是Robot的全限定名,而optmusPrime和bumblebee是Robot的实现类。
代码语言:javascript复制optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
接着我们进行测试:
代码语言:javascript复制public class JavaSPITest {
@Test
public void sayHello() throws Exception {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);
}
}
会输出optimusPrime和bumblebee的sayHello方法的结果。
我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。
接下来我们去解析《ExtensionLoader》对象的每个属性。这些属性是笔者在阅读各个方法的时候自己推测出来的,所以光看可能结果会很懵逼,需要结合方法去解读。
代码语言:javascript复制private final Class<?> type; // 这个loader类实际要loader的接口类型
private final ExtensionFactory objectFactory; // 用于获取自适应类的工厂
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>(); // 用于保存class, name的键值对,一个class只会保存一次
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>(); // 用于保存name , class的键值对,并且一个name只能对应一个class,否则报错
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); // 保存激活器对应的name和激活器本身(激活器类似于一个注解,实现aop)
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); // 用于保存name和holder的键值对,holder用于保存扩展类
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); // 自使用扩展类的实例
private volatile Class<?> cachedAdaptiveClass = null; // 自适应扩展类的存储
private String cachedDefaultName; // @SPI的value,用来保存默认的扩展类的名字
private volatile Throwable createAdaptiveInstanceError;
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>(); // 用于存储异常信息,根据配置文件中的"key = value"获取对应的异常信息
下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。
getExtension方法很简单,首先检查缓存,如果缓存未命中则创建拓展对象。
代码语言:javascript复制public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
// 获取默认的拓展实现类
return getDefaultExtension();
}
// Holder,顾名思义,用于持有目标对象
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 中
holder.set(instance);
}
}
}
return (T) instance;
}
接下来我们解析创建拓展对象的过程
代码语言:javascript复制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);
}
// 向实例中注入依赖
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
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);
}
}
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
1.通过 getExtensionClasses 获取所有的拓展类
2.通过反射创建拓展对象
3.向拓展对象中注入依赖
4.将拓展对象包裹在相应的 Wrapper 对象中
步骤1是加载拓展类的关键,步骤3,4是dubbo 的 ioc 和 aop的表现
下面我们来重点分析getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。
代码语言:javascript复制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;
}
这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。下面分析 loadExtensionClasses 方法的逻辑。
代码语言:javascript复制private Map<String, Class<?>> loadExtensionClasses() {
// 这里的type 在ExtensionLoader的构造函数就已经传进来了
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
// 获取SPI注解配置的默认拓展类别名
String value = defaultAnnotation.value();
if ((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));
}
// 如果只有一个默认扩展类的别名,则缓存的默认名称为这个值(参考getDefaultExtension)
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 加载指定路径下的文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI的解析也就是看看是否存在默认拓展类的别名,如果存在并且合法,则缓存默认别名。
接下来我们来解析loadDirectory方法。
代码语言:javascript复制private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
// fileName = 文件路径 类的全限定名称
String fileName = dir type.getName();
try {
Enumeration<java.net.URL> urls;
// 找到ExtensionLoader类的classloader
ClassLoader classLoader = findClassLoader();
// 找得到classLoader则用它来加载所有的同名配置文件
if (classLoader != null) {
urls = classLoader.getResources(fileName);
// 找不到则用默认的APPClassLoader
} else {
urls = ClassLoader.getSystemResources(fileName);
}
// 配置文件中的
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 加载资源
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: "
type ", description file: " fileName ").", t);
}
}
loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。
我们继续解析loadResource
代码语言:javascript复制private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
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) {
// 加载类,并通过 loadClass 方法对类进行缓存
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);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: "
type ", class file: " resourceURL ") in " resourceURL, t);
}
}
loadResource用于解析配置文件,#是注释,通过 = 号划分别名和类全限定名。通过反射加载类,最后调用loadClass方法进行其他操作。loadClass方法主要用于缓存,
接下来我们来解析loadClass方法
代码语言:javascript复制private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// 如果这个类不是type的实现类,则报错
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: "
type ", class line: " clazz.getName() "), class "
clazz.getName() "is not subtype of interface.");
}
// 如果这个类是有Adaptive的注解 用于默认加载扩展类
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 判断是否已经存在Adaptive扩展类,如果不存在缓存起来,如果存在且不是本类则报错
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
cachedAdaptiveClass.getClass().getName()
", " clazz.getClass().getName());
}
// 检查类是否是wrapper类型 wrapper类型的构造函数参数只有type本身(也就是这个钩子的接口)
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
// 如果cachedWrapperClasses为空则初始化
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
// 存储 clazz 到 cachedWrapperClasses 缓存中
wrappers.add(clazz);
// 进入此分支说明clazz是一个普通的扩展类
} else {
// 检查clazz是有默认的构造方法,如果没有则抛异常
clazz.getConstructor();
if (name == null || name.length() == 0) {
// 如果name为空,则尝试从注解Extension中获取,或者使用小写的类名
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 (names != null && names.length > 0) {
// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
// 存储 name 到 Activate 注解对象的映射关系
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
// 存储Class 到名称的映射关系
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
// 存储名称到 Class 的映射关系
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " type.getName() " name " n " on " c.getName() " and " clazz.getName());
}
}
}
}
}
loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,该方法没有其他什么逻辑了。
到此,关于缓存类加载的过程就分析完了。整个过程没什么特别复杂的地方,接下来我们来聊一聊dubbo IOC
代码语言:javascript复制private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍历实例目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测set方法,且仅有一个参数,且为public的方法
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
/**
* 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];
try {
// 获取到set方法相应的属性名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() method.getName().substring(4) : "";
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
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;
}
在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
在2.x的代码中,ioc只支持set方法注入。