最近研读Apache开源项目Shenyu网关的源码,网关的多个核心组件加载都用到了SPI模块。本文就Shenyu中的SPI设计和源码实现进行分析。
什么是SPI
SPI就是Service Provider Interface,直译"服务提供方接口",是一种动态的服务发现机制,可以基于接口运行时动态加载接口的实现类(也就是接口编程 + 策略模式 + 配置文件的一种开发模式)。最常见的就是JDK内置的数据库驱动接口java.sql.Driver,不同的厂商可以对该接口完成不同的实现,例如MySQL(MySQL驱动包中的com.mysql.jdbc.Driver)、PostgreSQL(PostgreSQL驱动包中的org.postgresql.Driver)等等。

JDK内置的SPI使用方式如下:
- 在类路径的
META-INF/services目录创建一个以接口全限定名称命名的文件(本质是一个properties)文件,例如命名为java.sql.Driver
- 该文件中可以指定具体的实现类,也就是每个实现类的全类型限定名为单独一行,例如
META-INF/services/java.sql.Driver中:
# META-INF/services/java.sql.Driver文件内容
com.mysql.jdbc.Driver
org.postgresql.Driver
- 最后通过
java.util.ServiceLoader对该文件进行加载,实例化接口的对应实现类(这里隐含了一个约定,所有实现类必须提供无参构造函数)
底层的实现涉及到类加载、双亲委托模型等内容,这里就不展开。基于这种设计思路,很多主流框架了自实现了一套SPI扩展,例如Dubbo的SPI扩展模块,就是读取类路径下META-INF/services/dubbo目录的文件内容进行类加载。Shenyu-SPI模块也是沿用类似的设计思路。
shenyu-spi源码分析
shenyu-spi模块十分精炼,代码结构如下:
- shenyu-spi[module]
- org.apache.shenyu.spi[package]
-- ExtensionFactory
-- ExtensionLoader
-- Join
-- SPI
-- SpiExtensionFactory
这些类功能如下:
ExtensionFactory:SPI加载器工厂,本身也是一个SPI,用于基于SPI机制加载ExtensionLoader实例同时基于ExtensionLoader实例获取默认的SPI标识接口实现
SpiExtensionFactory:其实就是ExtensionFactory的一个实现类
SPI:标识注解,用于标识SPI,用于接口上
Join:标识注解,用于实现类上,用于标识该类加入SPI系统
ExtensionLoader:SPI加载器,类比java.util.ServiceLoader,用于加载SPI中接口的实现类
接下来细看每个类的 源码实现。
@SPI
org.apache.shenyu.spi.SPI作为一个标识注解,主要用于接口上,也就是只有使用了@SPI的接口才能被shenyu-spi加载。这个类的注释中描述到:所有SPI系统相关参考Apache Dubbo的实现(这一点比较合情理,其实SPI扩展已经是一种成熟的方案,实现上大同小异)。该注解只有一个方法:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SPI {
String value() default "";
}
唯一的value()方法用于指定默认的SPI实现(可选的),后文在展开ExtensionLoader时候会说明。
@Join
org.apache.shenyu.spi.Join也是一个标识注解,主要用在使用了@SPI注解的接口的实现类上,用于标识该类加入SPI系统中而后可以被ExtensionLoader加载。该注解也只有一个方法:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Join {
int order() default 0;
}
唯一的order()方法用于指定具体的顺序号,单个使用了@SPI的接口存在多个使用了@Join的实现类的时候,这个顺序号就确定了这些实现类实例的排序(顺序号小的排在前面)。
ExtensionLoader
ExtensionLoader就是"类型扩展加载器",就是整个SPI模块的核心。先看其成员属性:
public final class ExtensionLoader<T> {
private static final Logger LOG = LoggerFactory.getLogger(ExtensionLoader.class);
private static final String SHENYU_DIRECTORY = "META-INF/shenyu/";
private static final Map<Class<?>, ExtensionLoader<?>> LOADERS = new ConcurrentHashMap<>();
private final Class<T> clazz;
private final ClassLoader classLoader;
private final Holder<Map<String, ClassEntity>> cachedClasses = new Holder<>();
private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();
private String cachedDefaultName;
private final Comparator<Holder<Object>> holderComparator = (o1, o2) -> {
if (o1.getOrder() > o2.getOrder()) {
return 1;
} else if (o1.getOrder() < o2.getOrder()) {
return -1;
} else {
return 0;
}
};
private final Comparator<ClassEntity> classEntityComparator = (o1, o2) -> {
if (o1.getOrder() > o2.getOrder()) {
return 1;
} else if (o1.getOrder() < o2.getOrder()) {
return -1;
} else {
return 0;
}
};
public static class Holder<T> {
private volatile T value;
private Integer order;
}
static final class ClassEntity {
private String name;
private Integer order;
private Class<?> clazz;
private ClassEntity(final String name, final Integer order, final Class<?> clazz) {
this.name = name;
this.order = order;
this.clazz = clazz;
}
}
}
分析完成员属性,不难发现下面几点:
ExtensionLoader会存在一个全局的静态缓存LOADERS,缓存已经创建的ExtensionLoader实例以防止重复创建的性能开销
- 每个
@SPI标记的接口如果使用ExtensionLoader进行加载,都会生成一个全新的ExtensionLoader实例
@SPI标记的接口如果有多个实现,那么最终获取到这些实现实例的时候是有序的
接着看其构造函数和静态工厂方法:
private ExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
this.clazz = clazz;
this.classLoader = cl;
if (!Objects.equals(clazz, ExtensionFactory.class)) {
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getExtensionClassesEntity();
}
}
public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
Objects.requireNonNull(clazz, "extension clazz is null");
if (!clazz.isInterface()) {
throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
}
if (!clazz.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
}
ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
if (Objects.nonNull(extensionLoader)) {
return extensionLoader;
}
LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz, cl));
return (ExtensionLoader<T>) LOADERS.get(clazz);
}
public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
return getExtensionLoader(clazz, ExtensionLoader.class.getClassLoader());
}
ExtensionLoader使用了私有构造器,使 用了静态工厂方法和懒加载模式。初始化ExtensionLoader完成后并不会触发类加载工作,真正的扫描和加载行为延迟到调用getJoin系列方法执行,这里扫码和加载所有实现类信息的方法调用链:
private Map<String, ClassEntity> getExtensionClassesEntity() {
Map<String, ClassEntity> classes = cachedClasses.getValue();
if (Objects.isNull(classes)) {
synchronized (cachedClasses) {
classes = cachedClasses.getValue();
if (Objects.isNull(classes)) {
classes = loadExtensionClass();
cachedClasses.setValue(classes);
cachedClasses.setOrder(0);
}
}
}
return classes;
}
private Map<String, ClassEntity> loadExtensionClass() {
SPI annotation = clazz.getAnnotation(SPI.class);
if (Objects.nonNull(annotation)) {
String value = annotation.value();
if (StringUtils.isNotBlank(value)) {
cachedDefaultName = value;
}
}
Map<String, ClassEntity> classes = new HashMap<>(16);
loadDirectory(classes);
return classes;
}
private void loadDirectory(final Map<String, ClassEntity> classes) {
String fileName = SHENYU_DIRECTORY + clazz.getName();
try {
Enumeration<URL> urls = Objects.nonNull(this.classLoader) ? classLoader.getResources(fileName)
: ClassLoader.getSystemResources(fileName);
if (Objects.nonNull(urls)) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
loadResources(classes, url);
}
}
} catch (IOException t) {
LOG.error("load extension class error {}", fileName, t);
}
}
private void loadResources(final Map<String, ClassEntity> classes, final URL url) throws IOException {
try (InputStream inputStream = url.openStream()) {
Properties properties = new Properties();
properties.load(inputStream);
properties.forEach((k, v) -> {
String name = (String) k;
String classPath = (String) v;
if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
try {
loadClass(classes, name, classPath);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("load extension resources error", e);
}
}
});
} catch (IOException e) {
throw new IllegalStateException("load extension resources error", e);
}
}
private void loadClass(final Map<String, ClassEntity> classes,
final String name, final String classPath) throws ClassNotFoundException {
Class<?> subClass = Objects.nonNull(this.classLoader) ? Class.forName(classPath, true, this.classLoader) : Class.forName(classPath);
if (!clazz.isAssignableFrom(subClass)) {
throw new IllegalStateException("load extension resources error," + subClass + " subtype is not of " + clazz);
}
if (!subClass.isAnnotationPresent(Join.class)) {
throw new IllegalStateException("load extension resources error," + subClass + " without @" + Join.class + " annotation");
}
ClassEntity oldClassEntity = classes.get(name);
if (Objects.isNull(oldClassEntity)) {
Join joinAnnotation = subClass.getAnnotation(Join.class);
ClassEntity classEntity = new ClassEntity(name, joinAnnotation.order(), subClass);
classes.put(name, classEntity);
} else if (!Objects.equals(oldClassEntity.getClazz(), subClass)) {
throw new IllegalStateException("load extension resources error,Duplicate class " + clazz.getName() + " name "
+ name + " on " + oldClassEntity.getClazz().getName() + " or " + subClass.getName());
}
}
通过方法链getExtensionClassesEntity -> loadExtensionClass -> loadDirectory -> loadResources -> loadClass最终得到一个别名->实现类信息的映射,用于后续的实例化,见getJoin()方法:
public T getJoin(final String name) {
if (StringUtils.isBlank(name)) {
throw new NullPointerException("get join name is null");
}
Holder<Object> objectHolder = cachedInstances.get(name);
if (Objects.isNull(objectHolder)) {
cachedInstances.putIfAbsent(name, new Holder<>());
objectHolder = cachedInstances.get(name);
}
Object value = objectHolder.getValue();
if (Objects.isNull(value)) {
synchronized (cachedInstances) {
value = objectHolder.getValue();
if (Objects.isNull(value)) {
Holder<T> pair = createExtension(name);
value = pair.getValue();
int order = pair.getOrder();
objectHolder.setValue(value);
objectHolder.setOrder(order);
}
}
}
return (T) value;
}
private Holder<T> createExtension(final String name) {
ClassEntity classEntity = getExtensionClassesEntity().get(name);
if (Objects.isNull(classEntity)) {
throw new IllegalArgumentException("name is error");
}
Class<?> aClass = classEntity.getClazz();
Object o = joinInstances.get(aClass);
if (Objects.isNull(o)) {
try {
joinInstances.putIfAbsent(aClass, aClass.newInstance());
o = joinInstances.get(aClass);
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: "
+ aClass + ") could not be instantiated: " + e.getMessage(), e);
}
}
Holder<T> objectHolder = new Holder<>();
objectHolder.setOrder(classEntity.getOrder());
objectHolder.setValue((T) o);
return objectHolder;
}
从createExtension()方法中可以看到最终是使用反射方式实例化实现类,反射方法newInstance()要求该类必须提供无参构造函数,因为这里有一点隐含的约定:SPI实现类必须提供无参构造函数,否则会实例化失败。剩余的getDefaultJoin()和getJoins()是基于getJoin()方法进行扩展,功能并不复杂,这里就不展开分析了。另外,在getJoin()方法用到了多级缓存:
cachedInstances:通过别名就可以搜索到对应的实现类实例
joinInstances:别名查找失败,则加载所有实现类信息,然后通过别名定位实现类类型,再通过实现类类型查找或者创建并缓存实现类实例后更新cachedInstances缓存
到此,ExtensionLoader的源码分析完毕。这里再通过一个ExtensionLoader实例成员属性内存布局图可以加深理解:

ExtensionFactory
ExtensionFactory是工厂模式里面的工厂接口,该接口定义了一个获取SPI实现(默认实现,唯一)实例的方法:
@SPI("spi")
public interface ExtensionFactory {
<T> T getExtension(String key, Class<T> clazz);
}
接着看其实现类SpiExtensionFactory的代码:
@Join
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(final String key, final Class<T> clazz) {
return Optional.ofNullable(clazz)
.filter(Class::isInterface)
.filter(cls -> cls.isAnnotationPresent(SPI.class))
.map(ExtensionLoader::getExtensionLoader)
.map(ExtensionLoader::getDefaultJoin)
.orElse(null);
}
}
这里值得注意的是:ExtensionFactory本身也是SPI系统的一部分。因此使用ExtensionFactory的时候可以直接实例化:
ExtensionFactory extensionFactory = new SpiExtensionFactory();
也可以基于ExtensionLoader进行加载:
# 在类路径META-INF/services/shenyu目录下添加一个属性文件org.apache.shenyu.spi.ExtensionFactory,内容是
spi=org.apache.shenyu.spi.SpiExtensionFactory
# 然后基于ExtensionLoader进行加载
ExtensionFactory extensionFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getDefaultJoin();
得到ExtensionFactory实例后就可以基于@SPI接口加载其默认实现类的实例。
扩展和建议
下面是个人的一些见解。目前来看,Shenyu中的SPI模块功能是完备的,建议考虑引入两个常用的功能:
- 可以在
Join注解中添加属性标记SPI接口实现类生成的实例是单例还是全新实例,类似于Spring中的Scope声明(singleton或者prototype)那样,例如:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Join {
int order() default 0;
boolean isSingleton() default true;
}
- 可选地让
SPI的实现类实现一个初始化器接口,在该实现类实例化后回调初始化器接口方法,例如:
public interface ExtensionInitializer {
void init();
}
@SPI
public interface JdbcSPI {
String getClassName();
}
@Join
public class MysqlSPI implements JdbcSPI, ExtensionInitializer {
@Override
public void init() {
}
@Override
public String getClassName() {
return "mysql";
}
}
如果添加了这两点,能够满足很多现实场景的需求。另外,ExtensionLoader中的两处比较器成员变量可以进行代码精简,例如对classEntityComparator而言:
private final Comparator<ClassEntity> classEntityComparator = (o1, o2) -> {
if (o1.getOrder() > o2.getOrder()) {
return 1;
} else if (o1.getOrder() < o2.getOrder()) {
return -1;
} else {
return 0;
}
};
private final Comparator<ClassEntity> classEntityComparator = Comparator.comparing(ClassEntity::getOrder);
基于Java原生SPI设计思路上设计出来的SPI框架具备了松耦合、高易用性和高扩展性的特点,并且添加了加载实例缓存、并发安全等特性,填补了原生JDK中SPI的一些缺陷,Shenyu的SPI模块也是如此。正是由于此强大的SPI模块的存在,Shenyu中的其他模块如Plugin模块可以实现快速插拔式配置,让加载一个全新开发的插件实例变得更加容易。