Dubbo为了达到OCP原则(对拓展开放,对修改封闭原则),采用了微内核+插件的架构
微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展内核系统的功能
内核可以采用Factory,IOC,OSGi等方式管理插件生命周期,Dubbo最终采用SPI机制来加载插件
参考JDK SPI并进行了性能优化以及功能增强
JDK SPI
什么是SPI
使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,不同数据库产品底层的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制在实际运行过程中,为 java.sql.Driver 接口寻找具体的实现。
JDK SPI机制
当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。
源码分析
JDK SPI 入口方法 ServiceLoader.load()
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程的classloader,假如classLoader为空则使用SystemClassLoader
// loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}</pre>
然后调用reload
// Cached providers, in instantiation order
// 缓存,用来缓存 ServiceLoader 对象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
// 清空缓存
providers.clear();
// 迭代器
lookupIterator = new LazyIterator(service, loader);
}</pre>
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 加载META-INF/services/下的配置文件
String fullName = PREFIX + service.getName();
// 加载配置文件
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// 按行SPI遍历配置文件的内容
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置文件
pending = parse(service, configs.nextElement());
}
// 更新 nextName 字段
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 加载 nextName 字段指定的类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
// 检测类型
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 创建实现类的对象
S p = service.cast(c.newInstance());
// 将实现类名称以及相应实例对象添加到缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public Iterator<S> iterator() {
return new Iterator<S>() {
// 用来迭代providers缓存
Iterator<Map.Entry<String,S>> 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();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
流程解析

应用场景
例如 JDBC 的应用,在 mysql-connector-java-*.jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容
<pre data-language="java" id="XG0vJ" style="border: 1px solid #e8e8e8; border-radius: 2px; background: #f9f9f9; padding: 16px; font-size: 13px; color: #595959">com.mysql.cj.jdbc.Driver </pre>
Dubbo SPI
借鉴JDK SPI思想,实现了自身的一套SPI机制
Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。
-
META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
-
META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
-
META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。
同时SPI配置文件改为KV格式,保证了只需要根据需要加载其中一个实现类
Dubbo SPI核心实现
-
@SPI注解
-
@Adaptive 注解与适配器
-
自动包装特性
-
自动装配特性
-
@Activate 注解与自动激活特性
如果Dubbo中的某个接口被 @SPI 注解修饰时,就表示该接口是扩展接口,例如 org.apache.dubbo.rpc.Protocol 接口
@SPI("dubbo")
public interface Protocol {
// ....
}
// dubbo-rpc/dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol</pre>
<pre data-language="java" id="yUNuY" style="border: 1px solid #e8e8e8; border-radius: 2px; background: #f9f9f9; padding: 16px; font-size: 13px; color: #595959">// org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
public static final String NAME = "dubbo";
public static DubboProtocol getDubboProtocol() {
if (INSTANCE == null) {
// ExtensionLoader 处理 @SPI 注解
// ExtensionLoader.getExtensionLoader() 方法会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例(实现见下)
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);
}
return INSTANCE;
}
ExtensionLoader 三个核心字段
// LoadingStrategy 接口有三个实现(通过 JDK SPI 方式加载的)
// 分别对应前面介绍的三个 Dubbo SPI 配置文件所在的目录,且都继承了 Prioritized 这个优先级接口,默认优先级是 DubboInternalLoadingStrategy > DubboLoadingStrategy > ServicesLoadingStrateg
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
/**
* Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,
* 其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例
*/
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
/**
* 该集合缓存了扩展实现类与其实例对象的映射关系。
* 即扩展点配置文件里面的kv
* 在前文示例中,Key 为 Class,Value 为 DubboProtocol 对象
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
getExtensionLoader 实现原理
/**
* 会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例
*/
@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!");
}
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;
}
getExtension 实现原理
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// getOrCreateHolder()方法中封装了查找cachedInstances缓存的逻辑
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
// double-check防止并发问题
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 根据扩展名从SPI配置文件中查找对应的扩展实现类
// 在 createExtension() 方法中完成了 SPI 配置文件的查找以及相应扩展实现类的实例化,同时还实现了自动装配以及自动 Wrapper 包装等功能
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension 实现原理
@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
// 获取 cachedClasses 缓存,根据扩展名从 cachedClasses 缓存中获取扩展实现类。
// 如果 cachedClasses 未初始化,则会扫描前面介绍的三个 SPI 目录获取查找相应的 SPI 配置文件,
// 然后加载其中的扩展实现类,最后将扩展名和扩展实现类的映射关系记录到 cachedClasses 缓存中。
// 这部分逻辑在 loadExtensionClasses() 和 loadDirectory() 方法中。
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// 根据扩展实现类从 EXTENSION_INSTANCES 缓存中查找相应的实例。如果查找失败,会通过反射创建扩展实现对象。
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 自动装配扩展实现对象中的属性(即调用其 setter)。这里涉及 ExtensionFactory 以及自动装配的相关内容,
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)) {
// 自动包装扩展实现对象。这里涉及 Wrapper 类以及自动包装特性的相关内容,本课时后面会进行详细介绍。
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));
}
}
}
}
// 如果扩展实现类实现了 Lifecycle 接口,在 initExtension() 方法中会调用 initialize() 方法进行初始化
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
以上是Dubbo如何通过@SPI注解找到对应的实现类,接下来讲解下@Adaptive注解的适配器实现
在ExtensionLoader.createExtension()方法中,扫描SPI配置文件时,会调用loadClass()方法加载SPI配置文件中指定的类
classes = loadExtensionClasses();
加载class流程
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)) {
// 处理@Adaptive注解
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
// 处理Wrapper类
cacheWrapperClass(clazz);
} else {
// 处理真正的扩展实现类
// 扩展实现类必须有无参构造函数
clazz.getConstructor();
// 兜底:SPI配置文件中未指定扩展名称,则用类的简单名称作为扩展名
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)) {
// 将包含@Activate注解的实现类缓存到cachedActivates集合中
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 在cachedNames集合中缓存实现类->扩展名的映射
cacheName(clazz, n);
// 在cachedClasses集合中缓存扩展名->实现类的映射
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
流程解析

名词定义
扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
扩展点实现:实现了扩展接口的实现类。
SPI与策略模式的比较
如果从代码接入的级别来看,策略模式还是在原有项目中进行代码修改,只不过它不会修改原有类中的代码,而是新建了一个类。而 SPI 机制则是不会修改原有项目中的代码,其会新建一个项目,最终以 Jar 包引入的方式代码。
从这一点来看,无论策略模式还是 SPI 机制,他们都是将修改与原来的代码隔离开来,从而避免新增代码对原有代码的影响。但策略模式是类层次上的隔离,而 SPI 机制则是项目框架级别的隔离。
从应用领域来说,策略模式更多应用在业务领域,即业务代码书写以及业务代码重构。而 SPI 机制更多则是用于框架的设计领域,通过 SPI 机制提供的灵活性,让框架拥有良好的插件特性,便于扩展。
总结一下,策略模式与 SPI 机制有下面几点异同:
• 从设计思想来看。策略模式和 SPI 机制其思想是类似的,都是通过一定的设计隔离变化的部分,从而让原有部分更加稳定。
• 从隔离级别来看。策略模式的隔离是类级别的隔离,而 SPI 机制是项目级别的隔离。
• 从应用领域来看。策略模式更多用在业务代码书写,SPI 机制更多用于框架的设计。
网友评论