什么是SPI
从面向对象的角度来说,接口不应依赖与实现。就拿操作数据库来说,每种数据库的操作方式都是不同的,如果更换了数据库,那么我们的代码就要推翻重来,JDBC就是一套规范,定义了操作方式的基本接口,各数据库厂商去做具体的实现,这样程序就不依赖底层的实现。但是我们在编写程序的时候,怎么知道需要使用的是哪种数据库呢?这里就要说到JAVA中提到的SPI(Service Provider Interface),是JDK内置的一种动态服替换发现服务实现者机制。
JAVA实现SPI需要如下步骤:
(1)编写接口并实现
public interface Cmand {
public void execute();
}
public class ShutdownCommand implements Cmand {
public void execute() {
System.out.println("shutdown....");
}
}
public class StartCommand implements Cmand {
public void execute() {
System.out.println("start....");
}
}
public class SPIMain {
public static void main(String[] args) {
ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class);
System.out.println(loader);
for (Cmand Cmand : loader) {
Cmand.execute();
}
}
}
(2)在下创建文件**META-INF/services/com.unei.serviceloader.Cmand
**
com.unei.serviceloader.impl.ShutdownCommand
com.unei.serviceloader.impl.StartCommand
(3)使用ServiceLoader加载并执行。
public class SPIMain {
public static void main(String[] args) {
ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class);
System.out.println(loader);
for (Cmand Cmand : loader) {
Cmand.execute();
}
}
优点:使用 Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。应用进程可以根据实际业务情况启用或替换具体组件。
缺点:
(1)不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费
(2)获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类
(3)多个并发多线程使用 ServiceLoader 类的实例是不安全的
(4)加载不到实现类时抛出并不是真正原因的异常,错误很难定位。
Dubbo SPI
与JAVA SPI相比,Dubbo SPI做了一些改进和优化:
(1)JDK标准SPI会一次性实例化所有扩展点,如果没有用上也去加载,则浪费资源。
(2)如果扩展加载失败,则连接的名称都获取不到了。由于异常被捕获,导致问题很难排查。
(3)增加了对IOC和AOP的支持,一个扩展可以直接setter注入其他属性,并且可以做到按需加载。
下面就来聊聊Dubbo的SPI是如何使用的,我们从ExtensionLoader开始讲起。
ExtensionLoader
org.apache.dubbo.common.extension.ExtensionLoader#getExtensionLoader
通过getExtensionLoader获得一个ExtensionLoader实例,其中有三个方法:
- getActivateExtension:根据条件获取当前扩展可自动激活的实现
- getExtension:根据指定的名称获取实现
- getAdaptiveExtension:获取当前扩展的自适应实现
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
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;
}
加载器会先尝试从缓存中获取,缓存中没有的话会去 创建一个 ExtensionLoader。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
在构造函数中有两个参数,type是加载SPI 的Class,objectFactory 是扩展的工厂。ExtensionFactory 也是基于SPI创建的,接下来我们看 getAdaptiveExtension() 是或者获取到一个适配的ExtensionFactory,这就要发挥 Adaptive注解的作用了。
Adaptive
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get(); // (1)
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get(); // (2)
if (instance == null) {
try {
instance = createAdaptiveExtension(); // (3)
cachedAdaptiveInstance.set(instance); // (4)
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
(1)、(2)重试从缓存中获取
(3) 创建一个ExtensionFactory
(4)将创建的ExtensionFactory放到缓存中
我们重点看下**createAdaptiveExtension()**方法。
- createAdaptiveExtension() // (1)
- injectExtension() // (2)
- getAdaptiveExtensionClass() // (3)
-getExtensionClasses() //(4)
-loadExtensionClasses() // (5)
-createAdaptiveExtensionClass() //(6)
先看第三步,尝试获取自适应的ExtensionFactory,先尝试去获取有Adaptive注解的ExtensionFactory,获取不到的话会从第六步创建一个代理的Adaptive。
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
这一段代码就是加载META-INF/services、META-INF/dubbo/等目录下的文件。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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)) {
cacheAdaptiveClass(clazz); // (1)
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz); //(2)
} else {
clazz.getConstructor();
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)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
我们可以看到(1)如果加了Adaptive的会打上特殊标记,方便后期获取自适应的类。
(2)cacheWrapperClass是Dubbo中的AOP,提供了一种包装,是解耦先Wrapper需要如下规范:
- 该类要实现SPI接口
- 该类中要有SPI接口的引用
- 该类中必须含有一个含参的构造方法且参数只能有一个类型为SPI接口
- 在接口实现方法中要调用 SPI 接口引用对象的相应方法
- 该类名称以 Wrapper 结尾
- 只能有一个Wapper
加载的类中如果没有Adaptive的话,会调用createAdaptiveExtensionClass生产一个Adaptive。
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类,在SPI接口中,有一个方法上必须有@Adaptive注解,并且方法中含有一个URL参数,在URL参数中能获取一个实现。我们看下下面的SPI接口如何生成一个自适应Adaptive
@SPI("impl1")
public interface AddExt3 {
@Adaptive
String echo(URL url, String s);
}
public class AddExt3_ManualAdaptive implements AddExt3 {
public String echo(URL url, String s) {
AddExt3 addExt1 = ExtensionLoader.getExtensionLoader(AddExt3.class).getExtension(url.getParameter("add.ext3"));
return addExt1.echo(url, s);
}
}
package org.apache.dubbo.common.extension.ext8_add;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class AddExt3$Adaptive implements org.apache.dubbo.common.extension.ext8_add.AddExt3 {
public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("add.ext3", "impl1");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext8_add.AddExt3) name from url (" + url.toString() + ") use keys([add.ext3])");
org.apache.dubbo.common.extension.ext8_add.AddExt3 extension = (org.apache.dubbo.common.extension.ext8_add.AddExt3)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext8_add.AddExt3.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
}
为接口中每个有@Adaptive注解的方法生成默认实现(没有注解的方法生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后框架会使用不同的编译器,把实现类字符串编译为自适应类并返回。生成代码逻辑如下:
(1)生成package、import、类名等信息,其中import只会导入ExtensionLoader,其他类都是使用全路径方式。类名为”接口名 + $Adaptive“.
(2) 遍历接口中所有方法,获取方法的返回类型、参数等信息。
(3)生成参数校验代码。
(4)生成默认实现类名称。如果@Adaptive注解中没有默认值,则根据类名生成,如果类名为YyXx,则实现为yy.xx。
(5)生成获取具体扩展实现类。最终还是调用getExtension(extName)来获取自适应扩展类的真正实现。
(6)调用目标实现的代码。
Activate
此注解要从**getActivateExtension(URL url, String key, String group)**方法讲起,key是扩展点的key,group为激活的组,可以获取所有自动激活扩展点。流程如下:
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses(); // (1)
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) { // (2)
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup) // (3)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
exts.add(getExtension(name));
}
}
exts.sort(ActivateComparator.COMPARATOR); // (4)
}
List<T> usrs = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) { // (5)
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
usrs.add(getExtension(name)); // (6)
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs); // (7)
}
return exts;
}
(1)从文件中加载初始化所有扩展类实现的集合。
(2)遍历所有含有@Activate注解集合,获取activateGroup、activateValue激活的组合value信息。
(3)根据URL匹配条件,获得所有符合激活条件的扩展类实现,并加入集合中。
(4)根据@Activate中配置的order参数排序。
(3)遍历用户自定义扩展类名称,根据URL配置顺序,调整扩展点激活顺序,如URL为:test://localhost/test/ext=order1,default,则激活的顺序为order1、default。如何扩展名含有”-“则不会被激活。default代表激活所有的扩展点。
(4)返回所有激活类的集合。
ExtensionFactory
上面说的ExtensionLoader是SPI的核心,但是ExtensionFactory又是怎么创建出来的呢?ExtensionFactory是基于ExtensionFactory创建的,这个工厂接口也有SPI注解。
@SPI
public interface ExtensionFactory {
<T> T getExtension(Class<T> type, String name);
}
ExtensionFactory接口有多个实现,AdaptiveExtensionFactory这个实现上有@Adaptive注解,所以他会作为默认实现。AdaptiveExtensionFactory在构造函数中初始化所有实现到factories集合中,在getExtension的时候获取一个实现。
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
SpringExtensionFactory如何与Spring整合
public class SpringExtensionFactory implements ExtensionFactory {
private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
private static final ApplicationListener SHUTDOWN_HOOK_LISTENER = new ShutdownHookListener();
// (1)
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
DubboShutdownHook.getDubboShutdownHook().unregister();
}
BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
}
public <T> T getExtension(Class<T> type, String name) {
···
// (2)
for (ApplicationContext context : CONTEXTS) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
if (Object.class == type) {
return null;
}
// (3)
for (ApplicationContext context : CONTEXTS) {
···
return context.getBean(type);
···
}
return null;
}
(1) 在ReferenceBean和ServiceBean初始化的时候保存Spring上下文
(2)遍历所有Spring上下文,现根据名字重容器中查找
(3)名字没有查询到再根据类型查询
SpiExtensionFactory
SpiExtensionFactory主要就是获取扩展点接口对应的Adaptive实现类。如某个扩展点实现类ClassA有@Adaptive注解,则调用SpiExtensionFactory#getExtension直接返回ClassA实例。代码如下:
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
总结
本文简单分别介绍了 Java SPI 与 Dubbo SPI 用法,并对 Dubbo SPI 的加载拓展类的过程进行了分析。同时分析了Dubbo AOP的实现原理。如果文章中有错误不妥之处,希望大家指正。