SPI
作为一种服务发现机制,允许程序在运行时动态地加载具体实现类。因其强大的可拓展性,SPI
被广泛应用于各类技术框架中,例如 JDBC
驱动、Spring
和 Dubbo
等等。Dubbo
并未使用原生的 Java SPI
,而是重新实现了一套更加强大的 Dubbo SPI
。本文将简单介绍 SPI
的设计理念,通过示例带你体会 SPI
的作用,通过 Dubbo
获取拓展的流程图 和 源码分析 带你理解 Dubbo SPI
的工作原理。深入了解 Dubbo SPI
,你将能更好地利用这一机制为你的程序提供灵活的拓展功能。
SPI 简介 SPI
的全称是 Service Provider Interface
,是一种服务发现机制。一般情况下,一项服务的接口和具体实现,都是服务提供者编写的。在 SPI
机制中,一项服务的接口是服务使用者编写的,不同的服务提供者编写不同的具体实现。在程序运行时,服务加载器动态地为接口加载具体实现类。因为 SPI
具备“动态加载”的特性,我们很容易通过它为程序提供拓展功能。以 Java
的 JDBC
驱动为例,JDK 提供了 java.sql.Driver
接口,各个数据库厂商,例如 MySQL
、Oracle
提供具体的实现。
目前 SPI 的实现方式 大多是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类:
Java SPI:META-INF/services/full.qualified.interface.name
Dubbo SPI:META-INF/dubbo/full.qualified.interface.name
(还有其他目录可供选择)
Spring SPI: META-INF/spring.factories
SPI 示例 Java SPI 示例 定义一个接口 Animal
。
1 2 3 public interface Animal { void bark () ; }
定义两个实现类 Dog
和 Cat
。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Dog implements Animal { @Override public void bark () { System.out.println("Dog bark..." ); } } public class Cat implements Animal { @Override public void bark () { System.out.println("Cat bark..." ); } }
在 META-INF/services
文件夹下创建一个文件,名称为 Animal
的全限定名 com.moralok.dubbo.spi.test.Animal
,文件内容为实现类的全限定名,实现类的全限定名之间用换行符分隔。
1 2 com.moralok.dubbo.spi.test.Dog com.moralok.dubbo.spi.test.Cat
进行测试。
1 2 3 4 5 6 7 8 9 public class JavaSPITest { @Test void bark () { System.out.println("Java SPI" ); System.out.println("============" ); ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class); serviceLoader.forEach(Animal::bark); } }
测试结果
1 2 3 4 Java SPI ============ Dog bark... Cat bark...
Dubbo SPI 示例 Dubbo
并未使用原生的 Java SPI
,而是重新实现了一套功能更加强大的 SPI
机制。Dubbo SPI
的配置文件放在 META-INF/dubbo
文件夹下,名称仍然是接口的全限定名,但是内容是“名称->实现类的全限定名”的键值对,另外接口需要标注 SPI
注解。
1 2 dog = com.moralok.dubbo.spi.test.Dog cat = com.moralok.dubbo.spi.test.Cat
进行测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class DubboSPITest { @Test void bark () { System.out.println("Dubbo SPI" ); System.out.println("============" ); ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class); Animal dog = extensionLoader.getExtension("dog" ); dog.bark(); Animal cat = extensionLoader.getExtension("cat" ); cat.bark(); } }
测试结果
1 2 3 4 Dubbo SPI ============ Dog bark... Cat bark...
Dubbo 获取扩展流程图
Dubbo SPI 源码分析 获取 ExtensionLoader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap <>(64 );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!" ); } 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>) EXTENSION_LOADERS.get(type); if (loader == null ) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader <T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
这个方法包含了如下步骤:
参数校验。
从缓存 EXTENSION_LOADERS
中获取与拓展类对应的 ExtensionLoader
,如果缓存未命中,则创建一个新的实例,保存到缓存并返回。
“从缓存中获取,如果缓存未命中,则创建,保存到缓存并返回 ”,类似的 getOrCreate
的处理模式在 Dubbo
的源码中经常出现。
EXTENSION_LOADERS
是 ExtensionLoader
的静态变量,保存了“拓展类->ExtensionLoader
”的映射关系。
根据 name 获取 Extension 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public T getExtension (String name) { return getExtension(name, true ); } public T getExtension (String name, boolean wrap) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException ("Extension name == null" ); } if ("true" .equals(name)) { return getDefaultExtension(); } final Holder<Object> holder = getOrCreateHolder(name); 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; } private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap <>();private Holder<Object> getOrCreateHolder (String name) { Holder<Object> holder = cachedInstances.get(name); if (holder == null ) { cachedInstances.putIfAbsent(name, new Holder <>()); holder = cachedInstances.get(name); } return holder; }
这个方法中获取 Holder
和获取拓展实例都是使用 getOrCreate
的模式。
Holder
用于持有拓展实例。cachedInstances
是 ExtensionLoader
的成员变量,保存了“name->Holder
(拓展实例)”的映射关系。
创建 Extension 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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) EXTENSION_INSTANCES.get(clazz); if (instance == null ) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); if (wrap) { List<Class<?>> wrapperClassesList = new ArrayList <>(); if (cachedWrapperClasses != null ) { wrapperClassesList.addAll(cachedWrapperClasses); wrapperClassesList.sort(WrapperComparator.COMPARATOR); Collections.reverse(wrapperClassesList); } if (CollectionUtils.isNotEmpty(wrapperClassesList)) { for (Class<?> wrapperClass : wrapperClassesList) { Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class); if (wrapper == null || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } } } initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException ("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
这个方法包含如下步骤:
通过 getExtensionClasses
获取所有拓展类
通过反射创建拓展实例
向拓展实例中注入依赖
将拓展实例包装在适配的 Wrapper
对象中
初始化拓展实例
第一步是加载拓展类的关键,第三步和第四步是 Dubbo IOC
和 AOP
的具体实现。
最后拓展实例的结构如下图。
加载 Extension Class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 private Map<String, Class<?>> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null ) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null ) { classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; } private Map<String, Class<?>> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap <>(); for (LoadingStrategy strategy : strategies) { loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache" , "com.alibaba" ), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); } return extensionClasses; } private void cacheDefaultExtensionName () { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation == null ) { return ; } String value = defaultAnnotation.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 = names[0 ]; } } }
依次处理特定目录 代码参考旧版本更容易理解。处理过程在本质上就是依次加载 META-INF/dubbo/internal/
、META-INF/dubbo/
、META-INF/services/
三个目录下的配置文件,获取拓展类。
1 2 3 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY);
新版本使用原生的 Java SPI
加载 LoadingStrategy
,允许用户自定义加载策略。
DubboInternalLoadingStrategy
,目录 META-INF/dubbo/internal/
,优先级最高
DubboLoadingStrategy
,目录 META-INF/dubbo/
,优先级普通
ServicesLoadingStrategy
,目录 META-INF/services/
,优先级最低
1 2 3 4 5 6 7 8 private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();private static LoadingStrategy[] loadLoadingStrategies() { return stream(load(LoadingStrategy.class).spliterator(), false ) .sorted() .toArray(LoadingStrategy[]::new ); }
LoadingStrategy
的 Java SPI
配置文件
loadDirectory 方法 loadDirectory
方法先通过 classLoader
获取所有的资源链接,然后再通过 loadResource
方法加载资源。
新版本中 extensionLoaderClassLoaderFirst
可以设置是否优先使用 ExtensionLoader's ClassLoader
获取资源链接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private void loadDirectory (Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) { String fileName = dir + type; try { Enumeration<java.net.URL> urls = null ; ClassLoader classLoader = findClassLoader(); if (extensionLoaderClassLoaderFirst) { ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) { urls = extensionLoaderClassLoader.getResources(fileName); } } if (urls == null || !urls.hasMoreElements()) { if (classLoader != null ) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } } if (urls != null ) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ")." , t); } }
loadResource 方法 loadResource
方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass
方法进行其他操作。loadClass
方法用于操作缓存。
新版本中 excludedPackages
可以设置将指定包内的类都排除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 private void loadResource (Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL, boolean overridden, String... excludedPackages) { try { try (BufferedReader reader = new BufferedReader (new InputStreamReader (resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; String clazz = null ; while ((line = reader.readLine()) != null ) { final int ci = line.indexOf('#' ); if (ci >= 0 ) { line = line.substring(0 , ci); } line = line.trim(); if (line.length() > 0 ) { 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)) { 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("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } }
loadClass 方法 loadClass
方法设置了多个缓存,比如 cachedAdaptiveClass
、cachedWrapperClasses
、cachedNames
和 cachedClasses
。
新版本中 overridden
可以设置是否覆盖 cachedAdaptiveClass
、cachedClasses
的 name->clazz
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 private void loadClass (Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) 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, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } 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, overridden); } } } } private void cacheAdaptiveClass (Class<?> clazz, boolean overridden) { if (cachedAdaptiveClass == null || overridden) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException ("More than 1 adaptive class found: " + cachedAdaptiveClass.getName() + ", " + clazz.getName()); } } private void cacheWrapperClass (Class<?> clazz) { if (cachedWrapperClasses == null ) { cachedWrapperClasses = new ConcurrentHashSet <>(); } cachedWrapperClasses.add(clazz); } private void cacheActivateClass (Class<?> clazz, String name) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null ) { cachedActivates.put(name, activate); } else { com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class); if (oldActivate != null ) { cachedActivates.put(name, oldActivate); } } }
Dubbo IOC Dubbo IOC
是通过 setter
方法注入依赖。Dubbo
首先通过反射获取目标类的所有方法,然后遍历方法列表,检测方法名是否具有 setter
方法特征并满足条件,若有,则通过 objectFactory
获取依赖对象,最后通过反射调用 setter
方法将依赖设置到目标对象中。
与 Spring IOC
相比,Dubbo IOC
实现的依赖注入功能更加简单,代码也更加容易理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 private T injectExtension (T instance) { if (objectFactory == null ) { return instance; } try { for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { continue ; } if (method.getAnnotation(DisableInject.class) != null ) { continue ; } Class<?> pt = method.getParameterTypes()[0 ]; if (ReflectUtils.isPrimitives(pt)) { continue ; } String property = getSetterProperty(method); Inject inject = method.getAnnotation(Inject.class); if (inject == null ) { injectValue(instance, method, pt, property); } else { if (!inject.enable()) { continue ; } if (inject.type() == Inject.InjectType.ByType) { injectValue(instance, method, pt, null ); } else { injectValue(instance, method, pt, property); } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; } private String getSetterProperty (Method method) { return method.getName().length() > 3 ? method.getName().substring(3 , 4 ).toLowerCase() + method.getName().substring(4 ) : "" ; } private boolean isSetter (Method method) { return method.getName().startsWith("set" ) && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers()); } private void injectValue (T instance, Method method, Class<?> pt, String property) { try { Object object = objectFactory.getExtension(pt, property); if (object != null ) { method.invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } }
objectFactory
是 ExtensionFactory
的自适应拓展,通过它获取依赖对象,本质上是根据目标拓展类获取 ExtensionLoader
,然后获取其自适应拓展,过程代码如下。具体我们不再深入分析,可以参考Dubbo SPI 自适应拓展的工作原理 。
1 2 3 4 5 6 7 8 9 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 ; }
参考文章