javaSpi
- java有spi机制为什么dubbo还要自创一套?
- javaSpi没有key value机制,没有顺序之分
- javaSpi 如果有多个实现类,只能依次加载,不能精准加载
- ...等
dubboSPI的特性
- 可根据指定的key获取SPI实现
- 可根据@Activate注解进行分类,获取指定的SPI实现
- 多个实现可排序
实现排序接口org.apache.dubbo.common.lang.Prioritized
- 可包装(静态代理)
对原有的spi实现直接编码进行静态的代理,spi的实现类只留一个有参构造,参数为SPI接口的类型即可变为wrapper
- 可注入
对spi的实现类中如果有set方法,且没有DisableInject注解,那么以此方法的第一个参数的类型+名称,再次从dubboSPI容器中寻找对应的实例。并set
- spi无实现者的情况下,可自适应实现(一般开发者用不到)
方法:getAdaptiveExtension()
如果spi配置文件中的配置实现类上有Adaptive注解,则直接用此类。
如果没有实现类,但是spi接口中个别方法上有Adaptive注解,并且参数有URL的类型(或者可从参数上获取到url),那么在调用的时候会根据adaptive注解的value,作为key从url中获取对应的value。然后在从SPI容器获取对应的实例,进行动态的调用
- ...等其他特性
使用例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ExtensionLoader<ABC> LOADER = ExtensionLoader.getExtensionLoader(ABC.class);
ABC defaultImpl = LOADER.getDefaultExtension();
ABC instance = LOADER.getExtension("key");
ABC adaptiveExtension = LOADER.getAdaptiveExtension();
List<T> activeExtension = LOADER.getActivateExtension(URL url, String[] values); activeExtension = LOADER.getActivateExtension(URL url, String[] values, String group); activeExtension = LOADER.getActivateExtension(URL url, String key, String group);
|
Activate
dubbo-spi中有3个方法,用来进行分类获取。
1 2 3
| List<T> getActivateExtension(URL url, String[] values); List<T> getActivateExtension(URL url, String[] values, String group); List<T> getActivateExtension(URL url, String key, String group);
|
注解
1 2 3 4 5 6 7
| public @interface Activate { String[] group() default {};
String[] value() default {}; ... int order() default 0; }
|
当dubbo-spi在加载实现类时,会判断实现类是否有该注解,如果有的话则会把这些实现类(带有注解的)缓存起来。
这样调用getActivateExtension时就会过滤条件并获取到对应的实现
wrapper
对原有对象的包装,类似于静态代理。可以拦截做很多事情。
如果spi实现类没有空构造方法并且有一个有参的构造函数,且类型是当前实现的接口。
那么在最终暴露的对象为此wrapper。
使用
使用图同Activate,不同点是,不需要加注解。并且需要一个有参的构造方法,参数类型是接口。同样配置在配置文件里面。即可完成配置
简单分析dubbo-spi的初始化
通过java-spi来找到dubboSpi配置的目录
1 2 3
| public interface LoadingStrategy extends Prioritized { String directory(); }
|
下图是dubbo的默认加载路径,咱们也可以通过javaSpi,扩展一个新的加载路径
dubboSpi配置目录下的文件和javaSPI的规范一样,都是以spi全类名作为文件名,不同之处就是内容。
dubboSpi配置文件的内容为key=value,key是一个名称,value为实现的class全类名,可以为多行
javaSpi配置文件的内容为多行,每行代表一个实现类的全类名名称。
dubbo用ExtensionLoader.getExtensionLoader(ABC.class).getExtension("key")
即可获取到对应的实例
加载并初始化的代码分析
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
| public class ExtensionLoader<T> { ... private static volatile LoadingStrategy[] strategies = stream(ServiceLoader.load(LoadingStrategy.class).spliterator(), false) .sorted() .toArray(LoadingStrategy[]::new);
private Map<String, Class<?>> getExtensionClasses() { 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 loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) { ... if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { String[] names = Pattern.compile("\\s*[,]+\\s*").split(name); cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n, overridden); } } } private T createExtension(String name, boolean wrap) { Class<?> clazz = getExtensionClasses().get(name); ... T instance = clazz.newInstance(); injectExtension(instance);
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>(); 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; } }
|
总结
dubboSPI比javaSPI多处好几个功能,我们一般都用不到,但是如果想要了解dubbo工作的整体流程。dubboSPI的代码是必须要掌握的。