SPI机制简介
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。
在源码阅读之前必须要理解的SPI机制中的注解。
@SPI 这个注解标识这是spi的一个扩展点,实现这个接口可以增加一个扩展机制,这个注解可以有默认使用的实现。例如@SPI(“spring”)默认使用spring容器。
@Adaptive 这个注解表明的是自适应的扩展类。Adaptive 可注解在类或方法上。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。
@Active 注解,标识激活的扩展类,可以设置只作用到某些角色上。比如只作用到消费者,或者生产者上。
自适应拓展机制介绍
该机制的官网介绍可以点击查看。下面简单描述一下我的理解:
每一个SPI拓展接口都可能会有很多种实现,不同的接口实现在不同的场景可能会用到,也可能用不到,那就不能一次性全部加载进来,不然会浪费资源。而且,具体使用哪个接口实现是运行时后才知道的,也就意味着,没办法提前知道只加载那些用到的实现。dubbo的自适应拓展机制就时为了解决这个问题,思路以Protoccol接口为例:
- Protoccol 接口有 DubboProtoccol RedisProtoccol HttpProtoccol等多种实现,现在需要一个中间类,对于用户来说是无感知的,这个类的方法调用能够通过传入的参数识别到用的是哪种具体实现,创建这个类就时自适应拓展机制的关键。
- 这个类对用户来说时无感知,那就是面向原有的接口,所以这个类应该实现Protoccol 接口,这样用户调用接口的方法返回具体实现就行。
- 这个类需要动态生成或者手动编码,这个类上就是 @Adaptive修饰的类或者类中的方法被这个注解修饰。
扩展点源码阅读
1.获取扩展的方法
dubbo中所有的SPI扩展点接口都被解析封装成一个 ExtensionLoader 对象,该对象持有扩展点的详细信息。获取具体的扩展点就是获取这个Loader对象。获取扩展对象的方法如下:
//TODO 获取spi扩展点
@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() + "!");
}
//先从缓存中获取扩展点
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;
}
这个方法很简单,入参是SPI注解标识的接口类对象,重点查看 new ExtensionLoader(type) 方法。
private ExtensionLoader(Class<?> type) {
this.type = type;
//先获取ExtensionFactory的所有扩展点,再从中获取自适应的扩展点loader
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
这里会将扩展的接口和扩展加载的类会被 ExtensionLoader 对象所持有。看getAdaptiveExtension方法实现:
//TODO 获取自适应扩展机制示例(每一个spi扩展点对应一个实例)
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//创建扩展点对应的实例,并进行缓存
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
再查看 createAdaptiveExtension方法:
//TODO 创建自适应扩展点实例
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
这个方法中先获取到了扩展类的Class对象,然后通过反射传教扩展类对象,最后注入这个类依赖的对象。
看看getAdaptiveExtensionClass如何创建对象:
private Class<?> getAdaptiveExtensionClass() {
//获取自适应扩展的所有实现类进行缓存
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//没有缓存则创建自适应扩展,通过代码生成类的字符串,再利用类加载器进行加载编译
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getExtensionClasses 方法
//TODO 对应扩展点的所有实现类,缓存到 Holder<Map<String, Class<?>>> cachedClasses中
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//加载spi所有的扩展类
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map<String, Class<?>> loadExtensionClasses() {
//缓存默认扩展名,SPI注解的value值,就是会使用的SPI实现
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// directory: "META-INF/dubbo/internal/" "META-INF/dubbo/" "META-INF/services/"
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
strategies时静态变量加载进来的,硬编码写死了spi机制的加载路径
// directory: “META-INF/dubbo/internal/” “META-INF/dubbo/” “META-INF/services/”
就是这几个。可以通过看下面的方法查看。
//TODO 通过JDK自带SPI机制获取到 META-INF/services/org.apache.dubbo.common.extension.LoadingStrategy 配置的加载策略
//加载策略中有以后的资源加载路径 directory: "META-INF/dubbo/internal/" "META-INF/dubbo/" "META-INF/services/"
private static LoadingStrategy[] loadLoadingStrategies() {
return stream(load(LoadingStrategy.class).spliterator(), false)
.sorted()
.toArray(LoadingStrategy[]::new);
}
通过查看下面的目录可以到加载的实现类,adaptive的就是手动编码的自适应扩展类。目前只有两个类分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。其余的都是自动生成

动态生成的方法如下:
private Class<?> createAdaptiveExtensionClass() {
//生成类的字符串
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
//查到类加载器
ClassLoader classLoader = findClassLoader();
//拿到编译器
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//编译创建实例
return compiler.compile(code, classLoader);
}
就是生成含有adaptive注解的方法的类,然后动态编译加载。最后所有的属性缓存都在ExtensionLoader中:
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
//TODO 缓存每一个spi扩展点接口与对应扩展点实例的映射
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
//TODO 扩展点接口类型
private final Class<?> type;
private final ExtensionFactory objectFactory;
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
//TODO 缓存所有spi接口所有的普通扩展实现类与名称的映射,在META-INF文件中定义的,名称可以多个对应一个,逗号隔开
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//TODO 缓存Activate注解的普通扩展类 Map<String, Object> cachedActivates
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
//TODO 自适应扩展类实例化对象缓存
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
//TODO 从MATE-INF 目录下加载的Adaptive注解标记的自适应扩展类,目前只有 AdaptiveCompiler 和 AdaptiveExtensionFactory
private volatile Class<?> cachedAdaptiveClass = null; //自适应扩展类,被Adaptive注解修饰
//TODO @SPI注解的value值,默认使用的扩展类
private String cachedDefaultName;
private volatile Throwable createAdaptiveInstanceError;
//TODO 缓存的扩展类带有有参构造函数,是一个装饰类,缓存到Set<Class<?>> cachedWrapperClasses
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
//TODO 通过jdk的SPI机制获取到加载策略,里面有加载spi的目录
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
本文深入剖析Dubbo的SPI机制,介绍了SPI的概念及其在Dubbo中的应用,包括自适应拓展机制、扩展点源码解析等内容,揭示了Dubbo如何通过SPI机制实现灵活的服务发现与扩展。
1万+

被折叠的 条评论
为什么被折叠?



