SPI机制简介
spi是一种服务发现机制,其目的是,在调用一个spi时,动态的选择不同的实现方式。这和api有很大区别,api是定死的,调用哪个api,其实现类的实现方式是一样的。而spi可以动态选择不同实现类作为实现。
其本质,是将接口实现类的全限定名配置在文件中,并由服务加载器读取,加载实现类,这样,就可以实现在运行时,动态为接口替换实现类。
常见的SPI机制
Java SPI、Spring SPI、Dubbo SPI
Java SPI
spi机制的配置文件保存在resources/META-INF.services下
文件名是interface的全限定名
例如:com.youlong.spi.ITestService
文件内容是接口实现类的全限定名列表
例如:
com.youlong.spi.impl.TestServiceImpl1 com.youlong.spi.impl.TestServiceImpl2
调用时使用ServiceLoader
进行加载,假设该接口中定义doTest
方法
例如:
java
ServiceLoader<ITestService> serviceLoader = ServiceLoader.load(ITestService.class);
serviceLoader.forEach(ITestService::doTest);
Java SPI并不能按需加载,容易造成资源浪费,并且只能通过iterator获取,不能通过某个参数来获取对应实现类。
Spring SPI
spi机制的配置文件保存在spring.factories文件,相比于java spi,spring的spi只要一个文件即可,所有spi实现对应关系都保存在一个文件,而java spi一个接口对应一个文件。
spring spi配置文件中,以kv形式保存
例如:
com.youlong.spi.ITestService =
com.youlong.spi.impl.TestServiceImpl1,
com.youlong.spi.impl.TestServiceImpl2
调用时使用SpringFactoriesLoader.loadFactories
来加载实现类列表
例如:
java
List<ITestService> testServices = SpringFactoriesLoader.loadFactories(ITestService.class, Thread.currentThread().getContextClassLoader())
testServices.forEach(ITestService::doTest);
spring spi和java spi类似,还是无法获取固定的某个实现类,只能按顺序获取所有实现。
Dubbo SPI
Dubbo SPI所需配置文件放在META-INF/dubbo
,命名按照接口的全限定名来命名
与Spring SPI和Java SPI配置不同,Dubbo SPI通过键值对的方式进行配置,K是实现方式命名,V是实现类全限定名
例子:
文件名:com.youlong.spi.ITestService
内容:
ini
testServiceImpl1 = com.youlong.spi.impl.TestServiceImpl1
testServiceImpl2 = com.youlong.spi.impl.TestServiceImpl2
com.youlong.spi.impl.TestServiceWrapper 注意:这个没有name的类,是AOP,dubbo的切面类
Dubbo SPI相关逻辑被封装在ExtensionLoader
类中,通过此类可以按需加载不同实现类。另外,在要实现SPI机制的接口上,要加上@SPI
注解,表示这个接口需要被扩展
获取所需实现类实例举例:
假设我们需要TestServiceImpl1
实现类
java
ExtensionLoader<ITestService> loader = ExtensionLoader.getExtensionLoader(ITestService);
ITestService testServiceImpl1 = loader.getExtension("testServiceImpl1");
testServiceImpl1.doTest();
Dubbo SPI除了支持按需加载接口实现类,还增加了IOC和AOP特性。
下面,我们来深入探究以下Dubbo SPI的实现原理
Dubbo SPI实现原理
上面提到了Dubbo SPI使用ExtensionLoader
来获取扩展点加载器
下面让我们看一下源码中getExtensionLoader
是如何实现的
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!");
}
// 接口没加@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>) extensionLoadersMap.get(type);
ExtensionScope scope = extensionScopeMap.get(type);
if (scope == null) {
SPI annotation = type.getAnnotation(SPI.class);
scope = annotation.scope();
extensionScopeMap.put(type, scope);
}
if (loader == null && scope == ExtensionScope.SELF) {
// create an instance in self scope
loader = createExtensionLoader0(type);
}
// 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;
}
这里特殊说明一下,扩展点加载器的本地缓存是ConsurrentMap类型的,也就是说它是线程安全的(注意:JavaSPI机制并不是线程安全的)
java
private final ConcurrentMap<Class<?>, ExtensionLoader<?>> extensionLoadersMap = new ConcurrentHashMap<>(64);
继续说getExtensionLoader
方法,会从本地缓存获取ExtensionLoader
,若获取不到,则回去new一个ExtensionLoader
并放入到本地缓存,最后将其返回
下面继续看下getExtension()
方法
java
public T getExtension(String name, boolean wrap) {
checkDestroyed();
// 名称为空则报错
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 名称为true,则加载默认扩展点,默认扩展点下面会讲
if ("true".equals(name)) {
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
// 这里用对象持有帮助对象Holder去辅助实现双检索单例
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);
}
}
}
return (T) instance;
}
默认扩展点:就是在接口的@SPI注解上定义的扩展点
例如:@SPI("testServiceImpl1")
默认扩展点就是testServiceImpl1
上述过程是去从Holder中获取一个扩展点实例(Holder也是一个ConcurrentHashMap
,key是实例name),获取不到,则双检索单例去创建,并set
给Holder
。
下面看一下createExtension()
是如何创建的(可以猜一下哎,肯定用了反射)
java
private T createExtension(String name, boolean wrap) {
// 先得到一个类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 根据实现类得到一个实例
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
// 在初始化前的处理
instance = postProcessBeforeInitialization(instance, name);
// 依赖注入IOC
injectExtension(instance);
// 后置处理
instance = postProcessAfterInitialization(instance, name);
}
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
// aop
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
boolean match = (wrapper == null) ||
((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
!ArrayUtils.contains(wrapper.mismatches(), name));
if (match) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// 检查instance是否实现了Lifecycle接口,若实现了,则调用initialize方法
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
这个方法里,会先获得实现类,然后根据实现类去创建实例
从配置文件获取实现类Map
我们首先来看下,是如何根据名字找到对应的实现类的 getExtensionClasses()
java
private Map<String, Class<?>> getExtensionClasses() {
// 获取所有的class
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
try {
// 加载扩展类
classes = loadExtensionClasses();
} catch (InterruptedException e) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", "Exception occurred when loading extension class (interface: " + type + ")", e);
throw new IllegalStateException("Exception occurred when loading extension class (interface: " + type + ")", e);
}
cachedClasses.set(classes);
}
}
}
return classes;
}
这个方法会获得一个包含了所有name
的对应的Class
这里,提前猜测一下,首先会获取文件中所有实现类的URL资源,然后根据类全限定名加载这些实现类,从而获得所有实现类的Map<name,Class>
。
接下来,看下加载扩展类
java
/**
* synchronized in getExtensionClasses
*/
@SuppressWarnings("deprecation")
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
checkDestroyed();
// 默认扩展器缓存(SPI注解里的name)
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载不同目录下的extensionClass
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;
}
此方法首先会对default扩展器进行处理,然后加载不同目录下的扩展器实现类。strategies
是不同目录的列表,是通过loadLoadingStrategies
方法加载获得的。代码如下
java
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
private static LoadingStrategy[] loadLoadingStrategies() {
return stream(load(LoadingStrategy.class).spliterator(), false)
.sorted()
.toArray(LoadingStrategy[]::new);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
看代码能知道,这下面用到Java SPI机制了,这里直接放配置文件看下
文件名:org.apache.dubbo.common.extension.LoadingStrategy
dart
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy
随便找一个实现类看下
java
public class DubboLoadingStrategy implements LoadingStrategy {
@Override
public String directory() {
return "META-INF/dubbo/";
}
其实就是一个文件目录path
我们回去继续看下loadDirectory
方法
java
private void loadDirectory(Map<String, Class<?>> extensionClasses, LoadingStrategy strategy, String type) throws InterruptedException {
loadDirectoryInternal(extensionClasses, strategy, type);
try {
// 这里是dubbo中自己定义的SPI接口用到了这里,我们自己定义的不需要管
String oldType = type.replace("org.apache", "com.alibaba");
if (oldType.equals(type)) {
return;
}
//if class not found,skip try to load resources
ClassUtils.forName(oldType);
loadDirectoryInternal(extensionClasses, strategy, oldType);
} catch (ClassNotFoundException classNotFoundException) {
}
}
上述方法去执行加载扩展实现类,后续是为了兼容老版本(先用此版本找一遍,再替换type前缀,去找一遍),替换的type名称,此处为dubbo内部的SPI,我们自己定义的,不用管。
loadDirectoryInternal
方法中会根据文件path来利用ClassLoader获取对应URL loadDirectoryInternal
方法关键代码如下
java
Set<ClassLoader> classLoaders = scopeModel.getClassLoaders();
if (CollectionUtils.isEmpty(classLoaders)) {
Enumeration<java.net.URL> resources = ClassLoader.getSystemResources(fileName);
if (resources != null) {
while (resources.hasMoreElements()) {
loadResource(extensionClasses, null, resources.nextElement(), loadingStrategy.overridden(),
loadingStrategy.includedPackages(),
loadingStrategy.excludedPackages(),
loadingStrategy.onlyExtensionClassLoaderPackages());
}
}
} else {
classLoadersToLoad.addAll(classLoaders);
}
在ClassLoader
通过fileName
获取URL
列表resources
后,执行loadResource
方法,如下
java
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String[] includedPackages, String[] excludedPackages, String[] onlyExtensionClassLoaderPackages) {
try {
List<String> newContentList = getResourceContent(resourceURL);
String clazz;
for (String line : newContentList) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
clazz = line.substring(i + 1).trim();
} else {
clazz = line;
}
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages) && isIncluded(clazz, includedPackages)
&& !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
// 将反射获得的Class加载进去
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type +
", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} catch (Throwable t) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", "Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
这个方法,就是将文件中的内容封装成一个newContentList
,之后分割,获取类的全限定名clazz
,之后,执行loadClass
方法,其中,会传入class.forName
获取到的Class
。
loadClass
代码如下
java
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) {
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, overridden);
// 判断是不是包装类,aop就是依据此模式实现的,判断条件是:若有构造方法,参数是这个接口的type,则是包装类。可以到这个方法源码中具体看一下。
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
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, overridden);
}
}
}
}
这个方法会对clazz
进行一系列校验,其中,isWrapperClass
用来判断是否是包装类,
若为包装类,则放到 Set<Class<?>> cachedWrapperClasses
本地缓存中,这也是Dubbo中AOP
的实现方式。目的是给我们这个接口的所有实现类增加一个切面。
到最后其实就是填充了extensionClasses
根据配置文件进行类加载过程已经结束,下面该进行实例化。
实现类的实例化
看下createExtensionInstance
下的instantiate
方法
java
public <T> T instantiate(Class<T> type) throws ReflectiveOperationException {
// should not use default constructor directly, maybe also has another constructor matched scope model arguments
// 1. try to get default constructor
Constructor<T> defaultConstructor = null;
try {
defaultConstructor = type.getConstructor();
} catch (NoSuchMethodException e) {
// ignore no default constructor
}
// 2. use matched constructor if found
List<Constructor<?>> matchedConstructors = new ArrayList<>();
Constructor<?>[] declaredConstructors = type.getConstructors();
for (Constructor<?> constructor : declaredConstructors) {
if (isMatched(constructor)) {
matchedConstructors.add(constructor);
}
}
// remove default constructor from matchedConstructors
if (defaultConstructor != null) {
matchedConstructors.remove(defaultConstructor);
}
// match order:
// 1. the only matched constructor with parameters
// 2. default constructor if absent
Constructor<?> targetConstructor;
if (matchedConstructors.size() > 1) {
throw new IllegalArgumentException("Expect only one but found " +
matchedConstructors.size() + " matched constructors for type: " + type.getName() +
", matched constructors: " + matchedConstructors);
} else if (matchedConstructors.size() == 1) {
targetConstructor = matchedConstructors.get(0);
} else if (defaultConstructor != null) {
targetConstructor = defaultConstructor;
} else {
throw new IllegalArgumentException("None matched constructor was found for type: " + type.getName());
}
// create instance with arguments
Class<?>[] parameterTypes = targetConstructor.getParameterTypes();
Object[] args = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
args[i] = getArgumentValueForType(parameterTypes[i]);
}
return (T) targetConstructor.newInstance(args);
}
这里会有一个获取targetConstructor
的过程,需要实现类中有无参构造方法,否则会报错。最后通过反射创建一个实例。
回到前面,创建完实例之后,有一个前置处理器(创建完实例之后,初始化之前),之后会进行依赖注入,然后进行AOP切面处理,再然后进行后置处理器(和spring bean的生命周期很像)
AOP切面的实现
我们来看下AOP切面处理的实现,createExtension
方法中有下面一行代码
java
// aop,这里得到的instance是Wrapper类的实例
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
此代码表示,再进行依赖注入时,传入的是一个实现该接口的Wrapper
类的实例(若配置了Wrapper类,则会走到这一步,若没有配置,则不会去执行这一步)
依赖注入的实现
下面看一下依赖注入的过程,Dubbo的依赖注入方式是setter
方式
java
private T injectExtension(T instance) {
if (injector == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
// 不是set方法,则跳过
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto-injection for this property
*/
// 若标记了不可注入注解,则跳过
if (method.isAnnotationPresent(DisableInject.class)) {
continue;
}
// When spiXXX implements ScopeModelAware, ExtensionAccessorAware,
// the setXXX of ScopeModelAware and ExtensionAccessorAware does not need to be injected
// 属于ScopeModelAware和ExtensionAccessorAware的set方法,则跳过
if (method.getDeclaringClass() == ScopeModelAware.class) {
continue;
}
if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
continue;
}
}
Class<?> pt = method.getParameterTypes()[0];
// 若set方法的参数是基本类型,则跳过
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = injector.getInstance(pt, property);
if (object != null) {
// 把所需的对象object注入到目标实例instance
method.invoke(instance, object);
}
} catch (Exception e) {
}
}
} catch (Exception e) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", e.getMessage(), e);
}
return instance;
}
这个方法完成了向instance
中依赖注入的工作,具体实现方式就是调用set
方法,往instance
里set
一个依赖实例
在createExtension()
方法的最后,回去检验此instance
是否实现了Lifecycle
接口,若是,则执行初始化方法。
至此,Dubbo SPI底层的实现原理解析就结束了。
总结
Dubbo SPI
机制中,配置文件中会保存实现类和Wrapper
类相关信息,要按需获取某一个扩展点时,首先,通过接口的type
去获取ExtensionLoader
,获取到之后,再根据name
获取Extension
扩展点实例,其底层会去读取配置文件,获取对应的实现类的全限定类名和Wrapper
类的全限定类名,再根据反射获取对应的Class
和WrapperClass(aop切面类
。以拿到name
和Class
的Map
。之后,会通过反射,将Class
进行实例化,得到一个个的空对象实例。 再依靠setter
方法进行依赖注入,依赖注入之后,会检查是否有Wrapper
包装类,若有,则创建包装类的实例,并将instance
当做参数传入到包装类中,再对此包装类实例进行依赖注入,完成这个工作,在调用扩展点的方法时,就会自动去调用包装类的方法,实现aop
切面的功能。在完成aop
处理之后,我们就可以得到一个完整的接口实现类对象(外层可能是个包装类)。