说实话我现在是不喜欢这种运行时扩展机制的,看似有很多选择很灵活,但其实只给一种选择在很多场景就是最优方案。但是没办法,SPI在dubbo中还真就绕不开。
dubbo-0x03-URL 已经介绍了URL,URL中承载了很多的配置信息,运行时根据URL中携带的配置参数选择对应的实现。
以zk的注册中心实现为例,看看怎么暴露出去,将来在应用中使用
ZookeeperRegistry由ZookeeperRegistryFactory负责创建
在约定的配置文件中配置上ZookeeperRegistryFactory
键是zookeeper,仅仅是给实现起的一个名字而已,不要跟其他实现重用就行
值是实现的全限定类名
ExtensionLoader拿着键去找对应实现的实例
dubbo的SPI实现机制都在ExtensionLoader中,从实现机制上来看,运行时实现可以分为
手动 自定义实现,打上Adaptive注解标识
自动 交给dubbo,dubbo通过SPI注解和Adaptive注解的组合创建代理,最终在URL的参数中拿到真正的实现别名
1 手动方式 这种肯定是不会存在的,如果使用这种方式就有悖于运行时动态获取实现了,相当于静态编码,所以压根不用看。
2 自动方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);" , type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); code.append(s);if (!rt.equals(void .class)) { code.append("\nreturn " ); } s = String.format("extension.%s(" , method.getName()); code.append(s);for (int i = 0 ; i < pts.length; i++) { if (i != 0 ) code.append(", " ); code.append("arg" ).append(i); } code.append(");" );
核心就是dubbo会先创建个代理对象,至于真正的实现对象,再通过SPI去找extName
对应的真正实现
还是用RegistryFactory
为例
2.1 RegistryFactory接口 1 2 3 4 5 @SPI("dubbo") public interface RegistryFactory { @Adaptive({"protocol"}) Registry getRegistry (URL url) ; }
2.2 ZookeeperRegistyFactory 写上配置文件,让dubbo的SPI可以扫描到
2.3 dubbo创建代理类 会尝试从URL中找protocol
对应的配置,没找到就用dubbo作为实现别名,在运行的时候用SPI去加载这个别名的真正的实现
2.4 扫描配置好的实现 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 private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null ) { 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 ) this .cachedDefaultName = names[0 ]; } } Map<String, Class<?>> extensionClasses = new HashMap <String, Class<?>>(); this .loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); this .loadDirectory(extensionClasses, DUBBO_DIRECTORY); this .loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
2.5 反射创建实例 1 2 3 4 5 6 7 8 Class<?> clazz = this .getExtensionClasses().get(name);if (clazz == null ) throw findException(name);try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null ) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz);
3 总结服务发现的策略
自己写个实现类打上@Adaptive
注解,SPI退化
接口上打@SPI
注解指定默认别名,方法打上@Adaptive
注解
没指定别名 -> 解析接口名,比如MyInterfaceName就被解析成my.interface.name
protocol特殊处理,直接url.getProtocol()拿到别名,再拿着别名去找实现
其他的用url.getParameter(xxx)拿到别名,再拿着别名去找实现
指定了key -> 用这个key去url.getParamter(key)
作别名去找实现,没找到再用@SPI
注解指定的别名去找