Dubbo 为了更好地达到 OCP 原则(即“对扩展开放,对修改封闭”的原则),采用了“微内核+插件”的架构。那什么是微内核架构呢?微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展内核系统的功能。微内核架构中,内核通常采用 Factory、IoC、OSGi 等方式管理插件生命周期,Dubbo 最终决定采用 SPI 机制来加载插件,Dubbo SPI 参考 JDK 原生的 SPI 机制,进行了性能优化以及功能增强。
一、SPI 机制原理当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。
package com.zhuangxiaoyan.dubbo;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;
/**
* @Classname Car
* @Description TODO
* @Date 2021/12/8 23:00
* @Created by xjl
*/
@SPI
public interface Car {
// @Adaptive
public void test(URL url);
}
package com.zhuangxiaoyan.dubbo;
import org.apache.dubbo.common.URL;
/**
* @Classname CarImpl
* @Description TODO
* @Date 2021/12/8 23:01
* @Created by xjl
*/
public class CarImpl implements Car {
@Override
public void test(URL url) {
System.out.println("SPI Test ……");
}
}
package com.zhuangxiaoyan.dubbo;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.net.MalformedURLException;
/**
* @Classname SpiTest
* @Description TODO
* @Date 2021/12/8 23:00
* @Created by xjl
*/
public class SpiTest {
public static void main(String[] args) throws MalformedURLException {
ExtensionLoader extensionLoader= ExtensionLoader.getExtensionLoader(Car.class);
URL url=new URL("http","localhost", 8080);
Car car=extensionLoader.getExtension("car");
car.test(null);
}
}
car=com.zhuangxiaoyan.dubbo.CarImpl
二、JDK SPI 源码分析
我们可以看到 JDK SPI 的入口方法是 ServiceLoader.load() 方法,在 ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法,调用关系如下图所示:
在 reload() 方法中,首先会清理 providers 缓存(LinkedHashMap 类型的集合),该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator 迭代器,用于读取 SPI 配置文件并实例化实现类对象。
ServiceLoader.reload() 方法的具体实现,如下所示:
// 缓存,用来缓存 ServiceLoader创建的实现对象
private LinkedHashMap providers = new LinkedHashMap();
public void reload() {
providers.clear(); // 清空缓存
lookupIterator = new LazyIterator(service, loader); // 迭代器
}
在前面的示例中,main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法,调用关系如下图所示:
首先来看 LazyIterator.hasNextService() 方法,该方法主要负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历,大致实现如下所示:
private static final String PREFIX = "META-INF/services/";
Enumeration configs = null;
Iterator pending = null;
String nextName = null;
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
// PREFIX前缀与服务接口的名称拼接起来,就是META-INF目录下定义的SPI配
// 置文件(即示例中的META-INF/services/com.xxx.Log)
String fullName = PREFIX + service.getName();
// 加载配置文件
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
// 按行SPI遍历配置文件的内容
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置文件
pending = parse(service, configs.nextElement());
}
nextName = pending.next(); // 更新 nextName字段
return true;
}
在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看 LazyIterator.nextService() 方法,该方法负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来,核心实现如下所示:
private S nextService() {
String cn = nextName;
nextName = null;
// 加载 nextName字段指定的类
Class c = Class.forName(cn, false, loader);
if (!service.isAssignableFrom(c)) { // 检测类型
fail(service, "Provider " + cn + " not a subtype");
}
S p = service.cast(c.newInstance()); // 创建实现类的对象
providers.put(cn, p); // 将实现类名称以及相应实例对象添加到缓存
return p;
}
以上就是在 main() 方法中使用的迭代器的底层实现。最后,我们再来看一下 main() 方法中使用ServiceLoader.iterator() 方法拿到的迭代器是如何实现的,这个迭代器是依赖 LazyIterator 实现的一个匿名内部类,核心实现如下:
public Iterator iterator() {
return new Iterator() {
// knownProviders用来迭代providers缓存
Iterator knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
// 先走查询缓存,缓存查询失败,再通过LazyIterator加载
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
// 先走查询缓存,缓存查询失败,再通过 LazyIterator加载
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
// 省略remove()方法
};
}
三、Dubbo SPI机制原理
- 扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
- 扩展点实现:实现了扩展接口的实现类。
JDK SPI 在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果 SPI 配置文件中定义了多个实现类,而我们只需要使用其中一个实现类时,就会生成不必要的对象。例如,org.apache.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol、ThriftProtocol 等多个实现,如果使用 JDK SPI,就会加载全部实现类,导致资源的浪费。
Dubbo SPI 不仅解决了上述资源浪费的问题,还对 SPI 配置文件扩展和修改。
首先,Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。
- META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
- META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
- META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。
然后,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。例如,这里指定扩展名为 dubbo,Dubbo SPI 就知道我们要使用:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。
使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。
四、Dubbo SPI 源码分析Dubbo 中某个接口被 @SPI注解修饰时,就表示该接口是扩展接口,前文示例中的 org.apache.dubbo.rpc.Protocol 接口就是一个扩展接口:
@SPI 注解的 value 值指定了默认的扩展名称,例如,在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,相关的 SPI 配置文件在 dubbo-rpc-dubbo 模块中,如下图所示:
该类是扩展加载器,这是dubbo实现SPI扩展机制等核心,几乎所有实现的逻辑都被封装在ExtensionLoader中。
1关于存放配置文件的路径变量:
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/";
"META-INF/services/"、"META-INF/dubbo/"、"META-INF/dubbo/internal/"三个值,都是dubbo寻找扩展实现类的配置文件存放路径,也就是我在上述(一)注解@SPI中讲到的以接口全限定名命名的配置文件存放的路径。区别在于"META-INF/services/"是dubbo为了兼容jdk的SPI扩展机制思想而设存在的,"META-INF/dubbo/internal/"是dubbo内部提供的扩展的配置文件路径,而"META-INF/dubbo/"是为了给用户自定义的扩展实现配置文件存放。
2扩展加载器集合,key为扩展接口,例如Protocol等:
private static final ConcurrentMap> EXTENSION_LOADERS = new ConcurrentHashMap>();
3扩展实现类集合,key为扩展实现类,value为扩展对象,例如key为
private static final ConcurrentMap, Object>();
4以下属性都是cache开头的,都是出于性能和资源的优化,才做的缓存,读取扩展配置后,会先进行缓存,等到真正需要用到某个实现时,再对该实现类的对象进行初始化,然后对该对象也进行缓存。
//以下提到的扩展名就是在配置文件中的key值,类似于“dubbo”等
//缓存的扩展名与拓展类映射,和cachedClasses的key和value对换。
private final ConcurrentMap, String>();
//缓存的扩展实现类集合
private final Holder>>();
//扩展名与加有@Activate的自动激活类的映射
private final Map cachedActivates = new ConcurrentHashMap();
//缓存的扩展对象集合,key为扩展名,value为扩展对象
//例如Protocol扩展,key为dubbo,value为DubboProcotol
private final ConcurrentMap cachedInstances = new ConcurrentHashMap 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);
//创建 Wrapper 扩展对象(自动包装)
Set pt = method.getParameterTypes()[0];
try {
//获得属性,比如StubProxyFactoryWrapper类中有Protocol protocol属性,
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
//获得属性值,比如Protocol对象,也可能是Bean对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//注入依赖属性
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;
}
4.10 getExtensionClass源码
获得扩展名对应的扩展实现类
private Class getExtensionClass(String name) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (name == null)
throw new IllegalArgumentException("Extension name == null");
Class clazz = getExtensionClasses().get(name);
if (clazz == null)
throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
return clazz;
}
4.11 getExtensionClasses源码
获得扩展实现类数组,这里思路就是先从缓存中取,如果缓存为空,则从配置文件中读取扩展实现类。
private Map> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//从配置文件中,加载扩展实现类数组
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
4.12 loadExtensionClasses源码
private Map> extensionClasses = new HashMap> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
4.16 createAdaptiveExtensionClass源码
创建适配器类,类似于dubbo动态生成的Transporter$Adpative这样的类。
private Class createAdaptiveExtensionClass() {
//创建动态生成的适配器类代码
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//编译代码,返回该类
return compiler.compile(code, classLoader);
}
这个方法中就做了编译代码的逻辑,生成代码在createAdaptiveExtensionClassCode方法中,createAdaptiveExtensionClassCode方法由于过长,我不在这边列出,下面会给出github的网址,读者可自行查看相关的源码解析。createAdaptiveExtensionClassCode生成的代码逻辑可以对照上述讲的注解@Adaptive中的Transporter$Adpative类来看。
4.17 AdaptiveExtensionFactory源码该类是ExtensionFactory的适配器类
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
//扩展对象的集合,默认的可以分为dubbo 的SPI中接口实现类对象或者Spring bean对象
private final List factories;
public AdaptiveExtensionFactory() {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List list = new ArrayList();
//遍历所有支持的扩展名
for (String name : loader.getSupportedExtensions()) {
//扩展对象加入到集合中
list.add(loader.getExtension(name));
}
//返回一个不可修改的集合
factories = Collections.unmodifiableList(list);
}
@Override
public T getExtension(Class type, String name) {
for (ExtensionFactory factory : factories) {
//通过扩展接口和扩展名获得扩展对象
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
- factories是扩展对象的集合,当用户没有自己实现ExtensionFactory接口,则这个属性就只会有两种对象,分别是 SpiExtensionFactory 和 SpringExtensionFactory 。
- 构造器中是把所有支持的扩展名的扩展对象加入到集合
- 实现了接口的getExtension方法,通过接口和扩展名来获取扩展对象。
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public T getExtension(Class type, String name) {
//判断是否为接口,接口上是否有@SPI注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
//获得扩展加载器
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
//返回适配器类的对象
return loader.getAdaptiveExtension();
}
}
return null;
}
}
4.19 ActivateComparator源码
该类在ExtensionLoader类的getActivateExtension方法中被运用到,作为自动激活拓展对象的排序器。
public class ActivateComparator implements Comparator {
public static final Comparator COMPARATOR = new ActivateComparator();
@Override
public int compare(Object o1, Object o2) {
//基本排序
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
if (o1.equals(o2)) {
return 0;
}
Activate a1 = o1.getClass().getAnnotation(Activate.class);
Activate a2 = o2.getClass().getAnnotation(Activate.class);
//使用Activate注解的 `after` 和 `before` 属性,排序
if ((a1.before().length > 0 || a1.after().length > 0
|| a2.before().length > 0 || a2.after().length > 0)
&& o1.getClass().getInterfaces().length > 0
&& o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
if (a1.before().length > 0 || a1.after().length > 0) {
String n2 = extensionLoader.getExtensionName(o2.getClass());
for (String before : a1.before()) {
if (before.equals(n2)) {
return -1;
}
}
for (String after : a1.after()) {
if (after.equals(n2)) {
return 1;
}
}
}
if (a2.before().length > 0 || a2.after().length > 0) {
String n1 = extensionLoader.getExtensionName(o1.getClass());
for (String before : a2.before()) {
if (before.equals(n1)) {
return 1;
}
}
for (String after : a2.after()) {
if (after.equals(n1)) {
return -1;
}
}
}
}
// 使用Activate注解的 `order` 属性,排序。
int n1 = a1 == null ? 0 : a1.order();
int n2 = a2 == null ? 0 : a2.order();
// never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
return n1 > n2 ? 1 : -1;
}
}
博文参考
深入理解Apache Dubbo与实战.pdf
https://segmentfault.com/a/1190000016842868
面试官问烂的Dubbo中SPI机制的源码解析_哔哩哔哩_bilibili