SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。用这个特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
1 SPI用法
1.1 普通扩展
1.1.1 创建带有SPI注解的接口与实现类
@SPI
public interface DemoSpi {
//此处的url在加载自适应扩展中可以用到
void say(URL url);
}
public class DemoSpiImpl implements DemoSpi{
Property property;
@Override
public void say(URL url) {
System.out.println("Hello DemoSpiImpl");
}
public Property getProperty() {
return property;
}
public void setProperty(Property property) {
this.property = property;
}
}
1.1.2 创建配置文件
在 META-INF 文件夹下创建dubbo文件夹,在dubbo文件夹下创建 com.shopping.demo24.dubbo.spi.DemoSpi 文件,内容如下:
demoSpiImpl=com.shopping.demo24.dubbo.spi.DemoSpiImpl
1.1.3 获取扩展
ExtensionLoader<DemoSpi> extensionLoader = ExtensionLoader.getExtensionLoader(DemoSpi.class);
URL url = URL.valueOf("demo://localhost/demo");
//通过扩展名获取扩展
DemoSpi demoSpi = extensionLoader.getExtension("demoSpiImpl");
demoSpi.say(url); //这里会输出 Hello DemoSpiImpl
1.2 自适应扩展
如果有多个扩展类时,自适应扩展可以根据配置或URL的值来加载特定的扩展。
扩展类默认是通过Javassist自动生成源码,然后加载的。在生成的代码中通过扩展名来获取具体的实现类,并调用其方法。所以此时可以通过 url 来控制具体调用的扩展时哪个。
生成的代码如下:
public class DemoSpi$Adaptive implements com.shopping.demo24.dubbo.spi.DemoSpi {
public void say(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("demo.spi", "d2");
if (extName == null)
throw new IllegalStateException("Failed to get extension (com.shopping.demo24.dubbo.spi.DemoSpi) name from url (" + url.toString() + ") use keys([demo.spi])");
ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), com.shopping.demo24.dubbo.spi.DemoSpi.class);
com.shopping.demo24.dubbo.spi.DemoSpi extension = (com.shopping.demo24.dubbo.spi.DemoSpi) scopeModel.getExtensionLoader(com.shopping.demo24.dubbo.spi.DemoSpi.class).getExtension(extName);
extension.say(arg0);
}
}
1.2.1 示例代码
在上一节的基础上再创建两个扩展类,给接口的SPI注解添加value,值为d2,给DemoSpiImpl1添加了注解@Adaptive。
@SPI("d2")
public interface DemoSpi {
@Adaptive({"key"})
void say(URL url);
}
@Adaptive
public class DemoSpiImpl1 implements DemoSpi{
@Override
public void say(URL url) {
System.out.println("Hello DemoSpiImpl1");
}
}
public class DemoSpiImpl2 implements DemoSpi{
@Override
public void say(URL url) {
System.out.println("Hello DemoSpiImpl2");
}
}
修改配置文件
d0=com.shopping.demo24.dubbo.spi.DemoSpiImpl
d1=com.shopping.demo24.dubbo.spi.DemoSpiImpl1
d2=com.shopping.demo24.dubbo.spi.DemoSpiImpl2
1.2.2 获取扩展的逻辑
1. 带有@Adaptive注解实现类的优先级最高。如果有实现带有@Adaptive注解,则获取的扩展不是动态生成的类,而是原始的实现类。
2. 如果URL参数中配置扩展名的话,最终方法调用会被代理到配置的扩展类实现上。如果接口方法上使用注解指定了key,则url中的参数需要配置为指定的key,如果没有指定,那默认的参数为 用"."分割的接口的小写名称。例如:上述示例注解为@Adaptive({"key"}),则url中的参数为 key=d0,这样调用方法时,获取名称为 d0 的扩展类。如果注解中没有指定,那url种可以配置为demo.spi=d0,这样获取的也是d0。
3. 如果url中没有指定,则默认获取的是 SPI 注解中指定的扩展。
4. 如果通过上面的步骤无法找到唯一的自适应扩展,就会抛出异常
java.lang.IllegalStateException: Failed to create adaptive instance: java.lang.IllegalStateException: Can't create adaptive extension interface com.shopping.demo24.dubbo.spi.DemoSpi, cause: No adaptive method exist on extension com.shopping.demo24.dubbo.spi.DemoSpi, refuse to create the adaptive class!
获取自适应扩展
// 配置自适应扩展
// 1.设置SPI注解的值为d2,@SPI("d2")
demoSpi=extensionLoader.getAdaptiveExtension();
demoSpi.say(url);
//2. url中的参数k与Adaptive中的参数相同,默认参数为 DemoSpi->demo.spi
url = URL.valueOf("demo://localhost/demo?demo.spi=d0");
url = URL.valueOf("demo://localhost/demo?key=d0");
demoSpi=extensionLoader.getAdaptiveExtension();
demoSpi.say(url);
//3.在类DemoSpiImpl1上添加@Adaptive,设置 SPI注解的值为d2,获取的是d1,此时获取的是实现类,而不是生成的类
// 所以 @Adaptive注解的优先级最高
demoSpi=extensionLoader.getAdaptiveExtension();
demoSpi.say(url);
1.3 Activate注解的使用
@Activita注解,顾名思义,就是当存在多个扩展时,激活某一个扩展。其定义如下:
public @interface Activate {
String[] group() default {};
String[] value() default {};
int order() default 0;
}
group与value可以在获取激活的扩展时用来过滤,order是对获取的扩展进行排序,值越小,在list中排的就越靠前。
1.3.1 实例代码
@SPI
public interface DemoExt {
void say();
}
@Activate(group = {"g1","g2"},value = {"k1:v1","k2,v2"},order = 2)
public class DemoExtImplA implements DemoExt{
@Override
public void say() {
System.out.println("DemoExtImplA");
}
}
@Activate(group = {"g1"},value = "k1:v1",order = 3)
public class DemoExtImplB implements DemoExt{
@Override
public void say() {
System.out.println("DemoExtImplA");
}
}
@Activate(group = {"g1","g3"},order = 1)
public class DemoExtImplC implements DemoExt{
@Override
public void say() {
System.out.println("DemoExtImplA");
}
}
1.3.2 配置文件
exta=com.shopping.demo24.dubbo.spi.activate.DemoExtImplA
extb=com.shopping.demo24.dubbo.spi.activate.DemoExtImplB
extc=com.shopping.demo24.dubbo.spi.activate.DemoExtImplC
1.3.3 获取激活的扩展
@Test
public void spiActivate() throws Exception {
ExtensionLoader<DemoExt> loader = ExtensionLoader.getExtensionLoader(DemoExt.class);
URL url = URL.valueOf("test://localhost/test");
// 1. 组参数为空,不会用组过滤,只会用value过滤
List<DemoExt> list = null;
list = loader.getActivateExtension(url, new String[]{}, null);
System.out.println(list);
// [com.shopping.demo24.dubbo.spi.activate.DemoExtImplC@301770d9,
// com.shopping.demo24.dubbo.spi.activate.DemoExtImplB@40edc64e]
//2. 组匹配
list = loader.getActivateExtension(url, new String[]{}, "g1");
System.out.println(list);
// [com.shopping.demo24.dubbo.spi.activate.DemoExtImplC@301770d9]
//3. 组与value,注解中如果设置了value,则要求value中的key:value必须与url中的相同,
// 注解中如果没有value,则不会校验参数,只用group匹配
url=url.addParameter("k1","v1");
list = loader.getActivateExtension(url, new String[]{}, "g1");
System.out.println(list);
// [com.shopping.demo24.dubbo.spi.activate.DemoExtImplC@e1fd2bf,
// com.shopping.demo24.dubbo.spi.activate.DemoExtImplA@301770d9,
// com.shopping.demo24.dubbo.spi.activate.DemoExtImplB@40edc64e]
}
1.3.4 激活的扩展匹配逻辑
- 如果注解中有value,则获取激活的扩展时,会通过value中的值与url中的参数做匹配。
- 如果没有value,当获取激活的扩展时传入group,就会通过group过滤,如果没有传入group,就不糊通过group过滤。
- 如果获取了多个扩展,则order值越小的在list中的位置越靠前。
2. 源码分析
2.1 获取ExtensionLoader
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(DemoSpi.class);
ExtensionDirector.java
@Override
@SuppressWarnings("unchecked")
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
checkDestroyed();
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() + "!");
}
// 1. find in local cache
// 从本地缓存获取
ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);
//获取扩展的范围,本例是 FRAMEWORK
ExtensionScope scope = extensionScopeMap.get(type);
if (scope == null) {
SPI annotation = type.getAnnotation(SPI.class);
scope = annotation.scope();
extensionScopeMap.put(type, scope);
}
//如果扩展范围是 SELF 自己,那就创建扩展加载器
if (loader == null && scope == ExtensionScope.SELF) {
// create an instance in self scope
loader = createExtensionLoader0(type);
}
//如果扩展范围不是自己,那就从父 ExtensionDirector(scoped extension loader manager) 获取加载器
// 2. find in parent
if (loader == null) {
if (this.parent != null) {
loader = this.parent.getExtensionLoader(type);
}
}
// 3. create it
// 没获取到的话创建
if (loader == null) {
loader = createExtensionLoader(type);
}
return loader;
}
private <T> ExtensionLoader<T> createExtensionLoader0(Class<T> type) {
checkDestroyed();
ExtensionLoader<T> loader;
//这里会new一个ExtensionLoader,然后添加到extensionLoadersMap中
extensionLoadersMap.putIfAbsent(type, new ExtensionLoader<T>(type, this, scopeModel));
loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);
return loader;
}
2.2 通过名称获取扩展
DemoSpi demoSpi = extensionLoader.getExtension("d0");
获取扩展时,先从Holder中获取,没有的话,创建扩展实例,再放入holder中。
获取扩展实例前,首先要加载扩展类。这里使用的加载策略是:DubboLoadingStrategy
DubboLoadingStrategy会在META-INF/dubbo/中找配置文件,最终会找到 META-INF/dubbo/com.shopping.demo24.dubbo.spi.DemoSpi 文件,然后加载
从配置类中解析出扩展的实现类,然后加载
获取所有扩展类后,根据名称获取需要的扩展类,然后实例化,返回扩展类的实例。
2.3 获取自适应扩展
demoSpi=extensionLoader.getAdaptiveExtension();
ExtensionLoader
T getAdaptiveExtension()
public T getAdaptiveExtension() {
//检查是否已经关闭
checkDestroyed();
//从缓存中获取实例
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;
}
T createAdaptiveExtension()
private T createAdaptiveExtension() {
try {
//获取扩展类的实例
T instance = (T) getAdaptiveExtensionClass().newInstance();
//初始化前处理
instance = postProcessBeforeInitialization(instance, null);
//注入依赖对象
injectExtension(instance);
//初始化后处理
instance = postProcessAfterInitialization(instance, null);
initExtension(instance);
return instance;
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
//获取自适应扩展类
private Class<?> getAdaptiveExtensionClass() {
//获取扩展类。从 META-INF/dubbo/com.shopping.demo24.dubbo.spi.DemoSpi配置文件中加载
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//创建自适应扩展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getExtensionClasses()->loadExtensionClasses()
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
checkDestroyed();
//缓存扩展名
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//用不同的加载策略加载
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
return extensionClasses;
}
从SPI注解上获取默认的扩展名
private void cacheDefaultExtensionName() {
//获取SPI注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
String value = defaultAnnotation.value();
//获取注解的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));
}
if (names.length == 1) {
//将值保存在cachedDefaultName中
cachedDefaultName = names[0];
}
}
}
生成自适应扩展类的源码,然后编译。生成的源码在上文中已经展示。生成代码时,会传入cachedDefaultName参数,生成的代码中会通过 url 中的参数找 扩展名,如果url中没有传参数,那么默认的参数就是 cachedDefaultName。
private Class<?> createAdaptiveExtensionClass() {
// Adaptive Classes' ClassLoader should be the same with Real SPI interface classes' ClassLoader
ClassLoader classLoader = type.getClassLoader();
try {
if (NativeUtils.isNative()) {
return classLoader.loadClass(type.getName() + "$Adaptive");
}
} catch (Throwable ignore) {
}
//生成源码,此时cachedDefaultName为 d2
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
//获取编译器
org.apache.dubbo.common.compiler.Compiler compiler = extensionDirector.getExtensionLoader(
org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//编译并加载
return compiler.compile(type, code, classLoader);
}
源码生成
AdaptiveClassCodeGenerator
public String generate(boolean sort) {
//检查是否有自适应
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
//生成包信息
code.append(generatePackageInfo());
//导入包
code.append(generateImports());
//生成类声明 public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
if (sort) {
Arrays.sort(methods, Comparator.comparing(Method::toString));
}
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append('}');
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
2.4 Activate注解的匹配
如果注解中value为空,直接返回true,如果不为空,则需要与url中的参数匹配
keyValue为注解中value的值中的value,realValue为通过注解中value的值中的key在url中获取的值
private boolean isActive(String[][] keyPairs, URL url) {
if (keyPairs.length == 0) {
return true;
}
for (String[] keyPair : keyPairs) {
// @Active(value="key1:value1, key2:value2")
String key;
String keyValue = null;
if (keyPair.length > 1) {
key = keyPair[0];
keyValue = keyPair[1];
} else {
key = keyPair[0];
}
String realValue = url.getParameter(key);
if (StringUtils.isEmpty(realValue)) {
realValue = url.getAnyMethodParameter(key);
}
if ((keyValue != null && keyValue.equals(realValue)) || (keyValue == null && ConfigUtils.isNotEmpty(realValue))) {
return true;
}
}
return false;
}