Dubbo系列-SPI扩展(上)
前言
SPI(Service Provider Interface)是一种动态替换实现的机制,解耦接口与实现,大大提高了程序的可扩展性。对于框架来说,开闭原则(OCP)非常赞的诠释。
关SPI会分成上、下两篇来分析,本篇会从JDK、Spring、Dubbo三个SPI实现分析其原理及三者的区别;下一篇会重点介绍关于Dubbo SPI的更多特性。
SPI
分别进行简单介绍,然后通过示例切入其核心代码实现,再小结其优缺点。
JDK SPI
JDK1.6版本提供了SPI支持,作为一个简单的服务提供工具,核心类 java.util.ServiceLoader,由于其是JDK内置的,不用额外实现,所以用的场景还有较多的,例如:mysql-connector-java-8.0.22.jar的Driver的实现加载。
一个小示例(代码不多,多个类文件放在一起),示例JDK版本11:
// 接口
// top.xudj.spi.jdk.JdkSimpleSpi
public interface JdkSimpleSpi {
void sayHello();
}
// 实现类1
// top.xudj.spi.jdk.JdkSimpleSpiImpl1
public class JdkSimpleSpiImpl1 implements JdkSimpleSpi {
@Override
public void sayHello() {
System.out.println("JdkSimpleSpiImpl1 say hello");
}
}
// 实现类2
// top.xudj.spi.jdk.JdkSimpleSpiImpl2
public class JdkSimpleSpiImpl2 implements JdkSimpleSpi {
@Override
public void sayHello() {
System.out.println("JdkSimpleSpiImpl2 say hello");
}
}
// 演示类
public class JdkSpiDemo {
/**
* jdk spi 调用流程:
* 1. 通过线程上下文类加载器加载 META-INF/services/下的配置文件
* 2. 通过配置文件中的类名实例化对象
* 3. 调用实例化对象的方法
*/
public static void main(String[] args) {
ServiceLoader<JdkSimpleSpi> serviceLoader = ServiceLoader.load(JdkSimpleSpi.class);
Iterator<JdkSimpleSpi> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
JdkSimpleSpi jdkSimpleSpi = iterator.next();
jdkSimpleSpi.sayHello();
}
}
}
配置,在Resources下创建文件:META-INF/services/top.xudj.spi.jdk.JdkSimpleSpi
top.xudj.spi.jdk.JdkSimpleSpiImpl1
top.xudj.spi.jdk.JdkSimpleSpiImpl2
文件配置要求:
1、需要特定目录:META-INF/services
2、文件名为接口/父类全限定名,文件内容为接口/子类实现全限定名,多个实现通过换行区分
运行演示类,输出:
JdkSimpleSpiImpl1 say hello
JdkSimpleSpiImpl2 say hello
原理分析,使用ServiceLoader调用load方法,传入接口Class,获取ServiceLoader对象。因ServiceLoader实现了Iterable,故可以进行遍历。下面看load方法。
// java.util.ServiceLoader#load(java.lang.Class<S>)
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = ClassLoader.getPlatformClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
默认使用当前线程的ClassLoader,另Reflection.getCallerClass()返回调用当前load的类,即JdkSpiDemo。
紧接着进行迭代:Iterator<JdkSimpleSpi> iterator = serviceLoader.iterator();
public Iterator<S> iterator() {
// 创建真实用于创建和获取对象的迭代器
if (lookupIterator1 == null) {
lookupIterator1 = newLookupIterator();
}
// 返回匿名Iterator实现类对象
return new Iterator<S>() {
// record reload count
final int expectedReloadCount = ServiceLoader.this.reloadCount;
// index into the cached providers list
int index;
/**
* Throws ConcurrentModificationException if the list of cached
* providers has been cleared by reload.
*/
private void checkReloadCount() {
if (ServiceLoader.this.reloadCount != expectedReloadCount)
throw new ConcurrentModificationException();
}
@Override
public boolean hasNext() {
checkReloadCount();
if (index < instantiatedProviders.size())
return true;
return lookupIterator1.hasNext();
}
@Override
public S next() {
checkReloadCount();
S next;
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
// 进入此处进行next获取
next = lookupIterator1.next().get();
instantiatedProviders.add(next);
}
index++;
return next;
}
};
}
S:表示范型,此处为JdkSimpleSpi接口
index:表示当前遍历的下标
instantiatedProviders:可以理解为存放实例的缓存
lookupIterator1:真实用于创建和获取对象的迭代器
两个核心逻辑,是否有下一个:lookupIterator1.hasNext();获取下一个的实例对象: next = lookupIterator1.next().get();
先看lookupIterator1是怎么样的迭代器,lookupIterator1 = newLookupIterator(); 返回迭代器:
private Iterator<Provider<S>> newLookupIterator() {
assert layer == null || loader == null;
if (layer != null) {
return new LayerLookupIterator<>();
} else {
Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
return new Iterator<Provider<S>>() {
@Override
public boolean hasNext() {
return (first.hasNext() || second.hasNext());
}
@Override
public Provider<S> next() {
if (first.hasNext()) {
return first.next();
} else if (second.hasNext()) {
return second.next();
} else {
throw new NoSuchElementException();
}
}
};
}
}
实际会调用到second.hasNext(),及 second.next()。
当调用second.hasNext()时,LazyClassPathLookupIterator会加载对应META-INF/services/下的文件,并将类全限定名转换成Class对象,创建ProviderImpl类对象传入Class对象构造器;
当调用second.next()时,获取到ProviderImpl类对象,再调用ProviderImpl对象的get方法:
// java.util.ServiceLoader.ProviderImpl#newInstance
private S newInstance() {
S p = null;
Throwable exc = null;
if (acc == null) {
try {
// 实例化
p = ctor.newInstance();
} catch (Throwable x) {
exc = x;
}
} else {
// ...
}
// ...
return p;
}
至此完成了实现类的实例化,并返回到iterator方法中的匿名迭代器实现的next方法,并将实例化的对象加入缓存instantiatedProviders中。
大致的流程如下:
值得注意的是:
如上是JDK11的源码,从JDK9开始,出现了Stream()方法,可以通过该方法完成类型比较而不用实例化,实现了按需实例化的能力。而在JDK8及之前,会实例化全部实现,无法做到按需加载。
JDK的SPI不限于针对接口,类、抽象类都可以。
Spring SPI
Spring SPI的实现比较简单,先看示例,示例Spring版本5.3.31。
// 接口,top.xudj.spi.spring.SpringSimpleSpi
public interface SpringSimpleSpi {
void sayHello();
}
// 实现1,top.xudj.spi.spring.SpringSimpleSpiImpl1
public class SpringSimpleSpiImpl1 implements SpringSimpleSpi {
@Override
public void sayHello() {
System.out.println("SpringSimpleSpiImpl1 say hello");
}
}
// 实现2,top.xudj.spi.spring.SpringSimpleSpiImpl2
public class SpringSimpleSpiImpl2 implements SpringSimpleSpi {
@Override
public void sayHello() {
System.out.println("SpringSimpleSpiImpl2 say hello");
}
}
// 演示类
public class SpringSpiDemo {
/**
* Spring spi 调用流程
* 1. 通过SpringFactoriesLoader.loadFactories方法加载SpringSimpleSpi接口的实现类
* 2. 调用实现类的sayHello方法
*/
public static void main(String[] args) {
List<SpringSimpleSpi> springSimpleSpiList = SpringFactoriesLoader.loadFactories(SpringSimpleSpi.class,
SpringSimpleSpi.class.getClassLoader());
springSimpleSpiList.forEach(SpringSimpleSpi::sayHello);
}
}
配置文件:META-INF/spring.factories,多个实现使用逗号分隔
top.xudj.spi.spring.SpringSimpleSpi=\
top.xudj.spi.spring.SpringSimpleSpiImpl1,top.xudj.spi.spring.SpringSimpleSpiImpl2
由配置文件可知,Spring的SPI配置都会放到spring.factories文件中。
文件配置要求:
1、需要特定目录和文件名:META-INF/spring.factories
2、文件内容为key-value方式,key表示接口的全限定名,value为接口实现全限定名,多个实现通过逗号分割
运行演示类,输出:
SpringSimpleSpiImpl1 say hello
SpringSimpleSpiImpl2 say hello
原理分析:Spring使用SpringFactoriesLoader进行加载获取配置在spring.factories的实现,并进行实例化,对应方法SpringFactoriesLoader#loadFactories
// org.springframework.core.io.support.SpringFactoriesLoader#loadFactories
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
// 获取当前类的类加载器
if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}
// 1、通过SpringFactoriesLoader.loadFactoryNames方法加载factoryType接口的实现类的全限定名集合
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
// ...
List<T> result = new ArrayList<>(factoryImplementationNames.size());
// 2、进行实例化
for (String factoryImplementationName : factoryImplementationNames) {
// instantiateFactory通过反射构造器实例化
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
//...
return result;
}
如果classLoader未传递,则使用SpringFactoriesLoader类对应的类加载器,所以这里类加载器也是关键的点,需要使用加载对应配置文件的类加载器。
该方法主要包括2步,第1步获取实现类的全限定名集合,第2步进行反射实例化。
第1步,获取某个Class的所有实现:List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
注意,这个Class的所有实现会加载ClassLoader下的所有的spring.factories文件,而非某一个文件,然后合并对应的实现列表。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 加载全部的spring.factories文件中的配置信息,然后根据factoryTypeName(配置文件中的key)获取对应的value集合
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
继续看loadSpringFactories(classLoader),完成配置文件的加载,返回Map<String, List<String>>
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 获取缓存中的数据
MultiValueMap<String, String> result = cache.get(classLoader);
// 如果缓存中有数据,则直接返回
if (result != null) {
return result;
}
try {
// 通过类加载器加载FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)文件
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
// 获取URL
URL url = urls.nextElement();
// 加载配置文件,获得Properties对象
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 将配置文件中的配置信息放入result中
String factoryTypeName = ((String) entry.getKey()).trim();
// 通过逗号分隔的字符串,将其转换为List,并遍历放入result中
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// factoryTypeName为key(全限定名),factoryImplementationName为value(全限定名)
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
// 将result放入缓存中,key为classLoader,所以会加载classLoader下的所有配置文件
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
优先查询缓存,缓存的key是ClassLoader,缓存中不存在,则查询该ClassLoader下全部的spring.factories文件,并循环进行处理。
返回的map,key为接口全限定名,value为spring.factories文件中配置的对应key的实现类的全部全限定名集合。
第2步,通过instantiateFactory方法完成实例化,并返回。
至此,Spring的SPI加载分析结束,相对比较简单。用的场景比较多的应该是Spring的自动装配了。和JDK一样都不限于接口。
Dubbo SPI
Dubbo的SPI相对比较复杂,在实现SPI的同时会进行依赖注入和包装(下一篇Dubbo SPI再进行介绍),同时缓存也较多。仍然从示例出发,看看Dubbo SPI是如何完成对应类实例化的。
示例Dubbo版本3.2.11,示例:
// 接口,top.xudj.spi.dubbo.DubboSimpleSpi
@SPI(value = DubboSimpleSpiImpl1.NAME, scope = ExtensionScope.APPLICATION)
public interface DubboSimpleSpi {
void sayHello();
}
// 实现1,top.xudj.spi.dubbo.DubboSimpleSpiImpl1
public class DubboSimpleSpiImpl1 implements DubboSimpleSpi {
public static final String NAME = "dubboSimpleSpiImpl1";
@Override
public void sayHello() {
System.out.println("DubboSimpleSpiImpl1 say hello");
}
}
// 实现2,top.xudj.spi.dubbo.DubboSimpleSpiImpl2
public class DubboSimpleSpiImpl2 implements DubboSimpleSpi {
public static final String NAME = "dubboSimpleSpiImpl2";
@Override
public void sayHello() {
System.out.println("DubboSimpleSpiImpl2 say hello");
}
}
// 演示类
public class DubboSpiDemo {
/**
* Dubbo spi 调用流程
* 1. 通过getExtensionLoader方法获取DubboSimpleSpi接口的ExtensionLoader
* 2. 通过getExtension方法获取DubboSimpleSpi接口的实现类
* 3. 调用实现类的sayHello方法
*/
public static void main(String[] args) {
ExtensionLoader<DubboSimpleSpi> extensionLoader = ApplicationModel.defaultModel().getExtensionLoader(DubboSimpleSpi.class);
DubboSimpleSpi dubboSimpleSpi = extensionLoader.getExtension(DubboSimpleSpiImpl2.NAME);
dubboSimpleSpi.sayHello();
}
}
配置文件:META-INF/dubbo/top.xudj.spi.dubbo.DubboSimpleSpi,文件内容:
dubboSimpleSpiImpl1=top.xudj.spi.dubbo.DubboSimpleSpiImpl1
dubboSimpleSpiImpl2=top.xudj.spi.dubbo.DubboSimpleSpiImpl2
文件配置要求:
1、文件目录支持三种,分别是META-INF/dubbo、META-INF/dubbo/internal、META-INF/services
2、文件名为接口的全限定名,内容为key-value方式,key是任意取的字符串,value为接口实现全限定名
运行演示类,输出:
DubboSimpleSpiImpl2 say hello
示例中提到ApplicationModel应用程序模型,是Dubbo3引入的领域模型,用于管理和区分不同的作用域的配置和服务,下一篇再进行分析。
原理分析:
总共包括2步,第1步获取到ExtensionLoader<DubboSimpleSpi>;第2步通过ExtensionLoader(String)获取到扩展类实现对象。
第1步:获得ExtensionLoader<DubboSimpleSpi>,通过getExtensionLoader(DubboSimpleSpi.class);方法会进入ExtensionDirector#getExtensionLoader的方法,如下:
Dubbo2版本ExtensionLoader存在getExtensionLoader(Class)静态方法,目前已经标记废弃
// org.apache.dubbo.common.extension.ExtensionDirector#getExtensionLoader
@Override
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
checkDestroyed();
// type不能为空
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// type必须是接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// type必须有@SPI注解
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,先从本地缓存中查找,extensionLoadersMap:ConcurrentMap<Class<?>, ExtensionLoader<?>>
ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);
// 从缓存中获取type对应的作用域,extensionScopeMap:ConcurrentMap<Class<?>, ExtensionScope>
ExtensionScope scope = extensionScopeMap.get(type);
// 如果缓存中不存在,则从SPI注解中获取作用域,并放入缓存
if (scope == null) {
// 默认ExtensionScope.APPLICATION
SPI annotation = type.getAnnotation(SPI.class);
scope = annotation.scope();
extensionScopeMap.put(type, scope);
}
// 判断:如果loader为空,并且作用域是SELF,则创建一个实例
if (loader == null && scope == ExtensionScope.SELF) {
// create an instance in self scope
// 创建ExtensionLoader实例,createExtensionLoader0会将loader放入extensionLoadersMap缓存
loader = createExtensionLoader0(type);
}
// 2. find in parent
if (loader == null) {
// 如果loader为空,且存在父级扩展管理器,则先从父级扩展管理器中查找(递归)
if (this.parent != null) {
loader = this.parent.getExtensionLoader(type);
}
}
// 3. create it
// 父类扩展管理器中也没有找到,则创建一个实例
// 注意:这里会先判断注解的作用域是否匹配如果父类/当前扩展管理器的作用域不匹配则不会创建
if (loader == null) {
loader = createExtensionLoader(type);
}
return loader;
}
首先进行一些判断,必须是接口,必须有@SPI注解;
然后查询缓存extensionLoadersMap,如果存在,则最后返回缓存中的loader;
紧接着如果接口对应的作用域是SELF,则会进行直接创建;
最后,类似于类加载器ClassLoader,递归委派给父类加载器进行获取loader,如果父类匹配了作用域则创建,否则返回null,由子类加载器匹配作用域决定是否创建。(创建的loader会加入缓存)
例如@SPI注解为ExtensionScope.Module,则委派过程ExtensionScope.Module -> Application -> Framework
1)、先ModuleScope缓存中查找,找到返回,找不到则进入2)
2)、再Application缓存中找,找到返回,找不到则匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配进入3)
3)、再Framework缓存中找,找到返回,找不到则匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配进入4)
4)、最后回到了ModuleScope匹配注解作用域,匹配则创建ExtensionLoader实例,不匹配不创建,返回null
第2步,得到了ExtensionLoader,通过ExtensionLoader(String)获取到扩展接口的实现对象。
// org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String, boolean)
// wrap默认为true
public T getExtension(String name, boolean wrap) {
// ...
// 如果传入的name为true,则返回默认的扩展实例
if ("true".equals(name)) {
// 获取默认的扩展实例
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
// 从缓存中获取扩展实例,并将holder存入cachedInstances缓存中,cachedInstances:ConcurrentMap<String, Holder<Object>>
final Holder<Object> holder = getOrCreateHolder(cacheKey);
Object instance = holder.get();
// 双重检查锁
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 核心方法:真正创建扩展实例的地方
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
// object转换为T类型
return (T) instance;
}
当name为“true“,调用getDefaultExtension返回默认扩展,即@SPI注解value值对应的类实现
进入核心方法:instance = createExtension(name, wrap);
// ExtensionLoader
private T createExtension(String name, boolean wrap) {
// 获取接口的全部扩展类实现class,然后根据name获取对应的class
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
// 从缓存中获取扩展实例,extensionInstances:ConcurrentMap<Class<?>, Object>
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 调用createExtensionInstance方法创建扩展实例,并放入缓存
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
// 前置处理,类似Spring的BeanPostProcessor#postProcessBeforeInitialization,可以在此处做一些扩展实例的前置处理
instance = postProcessBeforeInitialization(instance, name);
// 注入扩展实例的依赖,类似Spring的Autowired,只不过它是setter注入
injectExtension(instance);
// 后置处理,类似Spring的BeanPostProcessor#postProcessAfterInitialization,可以在此处做一些扩展实例的后置处理
instance = postProcessAfterInitialization(instance, name);
}
// 是否需要包装,默认为true,先忽略
// ...
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle
// instance, this application may not invoke the lifecycle.initialize hook.
// 如果扩展实现了Lifecycle接口,调用其初始化方法
initExtension(instance);
// 返回扩展实例
return instance;
} catch (Throwable t) {
throw new IllegalStateException(
"Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: "
+ t.getMessage(),
t);
}
}
这里面包括了3个核心步骤:
先从缓存extensionInstances获取,没有的话调用createExtensionInstance(clazz)进行创建
实例化后,进行前置处理、“依赖注入”、后置处理。与Spring的BeanPostProcessor如出一辙
如果扩展类实现了Lifecycle接口,调用其初始化方法
首先看createExtensionInstance(clazz),该方法就是使用构造器进行反射初始化。默认是无参构造器,但不是直接使用默认构造函数,可能还有另一个构造函数匹配了作用域模型参数。
其次实例化后,进行前置处理、“依赖注入”、后置处理。
前置处理:默认无逻辑;
依赖注入:类似Spring的Autowired,只不过它是setter注入,同时set方法对应的属性实例可以通过injector获取(scope bean和spi extension)。
不会进行依赖注入的情况:1)、对于DisableInject注解的属性不进行注入;2)、对于ScopeModelAware和ExtensionAccessorAware的set方法不进行注入(因为它们是通过postProcessAfterInitialization方法注入的)。
后置处理:如果实例实现了 ScopeModelAware 接口,则将 scopeModel 注入到实例中,类似Spring的ApplicationContextAware等
当然,也可以通过ExtensionDirector添加自定义的List<ExtensionPostProcessor>,便会调用对应的前后置处理
最后扩展类实现了Lifecycle接口,调用其初始化方法,这个Lifecycle为Dubbo内部的接口,而非Spring。
至此,完成了实例化及一些缓存的处理。同时实现了Dubbo自己的DI。wrap其实对应这个Dubbo实现的AOP,包括ExtensionLoader的其它获取扩展类的方法,后面文章再介绍。
区别
用一个表格来做一些比对吧
维度 | JDK SPI | Spring SPI | Dubbo SPI |
---|---|---|---|
难易程度 | 中 | 易 | 难 |
配置文件目录 | META-INF/services/接口全限定名 | META-INF/spring.factories | META-INF/dubbo/ META-INF/dubbo/internal/ META-INF/services/ 三种目录下都是接口全限定名 |
配置文件内容 | 直接是扩展类的全限定名; 多个使用换行 | key-value key:接口的全限定名 value:扩展类的全限定名 多个使用逗号 | key-value key:别名,任意字符串,不要用“true” value:扩展类的全限定名 |
扩展接口 | 接口或类 | 接口或类 | 接口,且必须有@SPI注解 |
按需实例化 | JDK8及之前,每次都是实例化全部扩展类; JDK9之后,可以通过stream方法进行Class过滤后实例化(按需) | 实例化对应扩展接口的全部扩展类 | 按名称进行实例化(按需) |
总结
从JDK到Spring再到Dubbo的SPI实现,都是为了解耦,将接口/类与扩展类进行分离,方便使用者进行扩展。对比下来,Dubbo的实现最为复杂,不仅实现了SPI的基础扩展,还增加了例如DI和AOP的功能,另外还有一些其它特性,可以看Dubbo系列-SPI扩展(下)文章。